⛏️ index : haiku.git

/*
 * Copyright 2005-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Copyright 2013, Rene Gollent, rene@gollent.com.
 * Distributed under the terms of the MIT License.
 */

#include "SymbolLookup.h"

#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <new>

#include <debug_support.h>
#include <runtime_loader.h>
#include <syscalls.h>

#include "Image.h"


#undef TRACE
//#define TRACE_DEBUG_SYMBOL_LOOKUP
#ifdef TRACE_DEBUG_SYMBOL_LOOKUP
#	define TRACE(x) printf x
#else
#	define TRACE(x) ;
#endif


using namespace BPrivate::Debug;


const void *
Area::TranslateAddress(const void *address)
{
	TRACE(("Area::TranslateAddress(%p): area: %" B_PRId32 "\n", address, fLocalID));

	// translate the address
	const void *result = (const void*)((addr_t)address - (addr_t)fRemoteAddress
		+ (addr_t)fLocalAddress);

	TRACE(("Area::TranslateAddress(%p) done: %p\n", address, result));

	return result;
}


// #pragma mark -


RemoteMemoryAccessor::RemoteMemoryAccessor(debug_context* debugContext)
	: fDebugContext(debugContext),
	  fAreas()
{
}

RemoteMemoryAccessor::~RemoteMemoryAccessor()
{
	// delete the areas
	while (Area *area = fAreas.Head()) {
		fAreas.Remove(area);
		delete area;
	}
}


status_t
RemoteMemoryAccessor::InitCheck() const
{
	// If we don't have a debug context, then there's nothing we can do.
	// SymbolLookup's image file functionality will still be available, though.
	if (fDebugContext == NULL || fDebugContext->nub_port < 0)
		return B_NO_INIT;

	return B_OK;
}


const void *
RemoteMemoryAccessor::PrepareAddress(const void *remoteAddress,
	int32 size)
{
	TRACE(("RemoteMemoryAccessor::PrepareAddress(%p, %" B_PRId32 ")\n",
		remoteAddress, size));

	if (remoteAddress == NULL) {
		TRACE(("RemoteMemoryAccessor::PrepareAddress(): Got null address!\n"));
		throw Exception(B_BAD_VALUE);
	}

	return _GetArea(remoteAddress, size).TranslateAddress(remoteAddress);
}


const void *
RemoteMemoryAccessor::PrepareAddressNoThrow(const void *remoteAddress,
	int32 size)
{
	if (remoteAddress == NULL)
		return NULL;

	Area* area;
	status_t status = _GetAreaNoThrow(remoteAddress, size, area);
	if (status != B_OK)
		return NULL;

	return area->TranslateAddress(remoteAddress);
}


Area*
RemoteMemoryAccessor::AreaForLocalAddress(const void* address) const
{
	if (address == NULL)
		return NULL;

	for (AreaList::ConstIterator it = fAreas.GetIterator(); it.HasNext();) {
		Area* area = it.Next();
		if (area->ContainsLocalAddress(address))
			return area;
	}

	return NULL;
}


Area &
RemoteMemoryAccessor::_GetArea(const void *address, int32 size)
{
	TRACE(("RemoteMemoryAccessor::_GetArea(%p, %" B_PRId32 ")\n", address,
		size));

	Area* area;
	status_t status = _GetAreaNoThrow(address, size, area);
	if (status != B_OK) {
		TRACE(("RemoteMemoryAccessor::_GetArea(): Failed to get address %p\n",
			address));
		throw Exception(status);
	}

	return *area;
}


status_t
RemoteMemoryAccessor::_GetAreaNoThrow(const void *address, int32 size, Area *&_area)
{
	for (AreaList::Iterator it = fAreas.GetIterator(); it.HasNext();) {
		Area *area = it.Next();
		if (area->ContainsAddress(address, size)) {
			_area = area;
			return B_OK;
		}
	}

	if (InitCheck() != B_OK)
		return B_NO_INIT;

	// we need to clone a new area
	debug_nub_clone_area message;
	message.reply_port = fDebugContext->reply_port;
	message.address = address;

	debug_nub_clone_area_reply reply;
	status_t error = send_debug_message(fDebugContext, B_DEBUG_MESSAGE_CLONE_AREA,
		&message, sizeof(message), &reply, sizeof(reply));
	if (error != B_OK)
		return error;

	area_id localID = reply.area;
	if (localID < 0) {
		TRACE(("RemoteMemoryAccessor: Failed to clone area for %p: %s\n",
			address, strerror(localID)));
		return localID;
	}

	area_info areaInfo;
	error = get_area_info(localID, &areaInfo);
	if (error < 0) {
		TRACE(("RemoteMemoryAccessor: Failed to get info for %" B_PRId32
			": %s\n", localID, strerror(error)));
		return error;
	}

	const addr_t remoteBaseAddress = (addr_t)address
		- ((addr_t)reply.address - (addr_t)areaInfo.address);

	Area *area = new(std::nothrow) Area(localID,
		remoteBaseAddress, areaInfo.address, areaInfo.size);
	if (area == NULL)
		return B_NO_MEMORY;

	fAreas.Add(area);

	_area = area;
	return B_OK;
}


// #pragma mark -


class SymbolLookup::LoadedImage : public Image {
public:
								LoadedImage(SymbolLookup* symbolLookup,
									const image_info& info,
									const image_t* image, int32 symbolCount);
	virtual						~LoadedImage();

	virtual	const elf_sym*		LookupSymbol(addr_t address,
									addr_t* _baseAddress,
									const char** _symbolName,
									size_t *_symbolNameLen,
									bool *_exactMatch) const;
	virtual	status_t			NextSymbol(int32& iterator,
									const char** _symbolName,
									size_t* _symbolNameLen,
									addr_t* _symbolAddress, size_t* _symbolSize,
									int32* _symbolType) const;

private:
			SymbolLookup*			fSymbolLookup;
			const image_t*			fImage;
			int32					fSymbolCount;
			size_t					fLoadDelta;
};


// #pragma mark -


SymbolLookup::SymbolLookup(debug_context* debugContext, image_id image)
	:
	RemoteMemoryAccessor(debugContext),
	fDebugArea(NULL),
	fImages(),
	fImageID(image)
{
}


SymbolLookup::~SymbolLookup()
{
	while (Image* image = fImages.RemoveHead())
		delete image;
}


status_t
SymbolLookup::Init()
{
	TRACE(("SymbolLookup::Init()\n"));

	status_t error = 0;

	if (RemoteMemoryAccessor::InitCheck() == B_OK) {
		TRACE(("SymbolLookup::Init(): searching debug area...\n"));

		// find the runtime loader debug area
		runtime_loader_debug_area *remoteDebugArea = NULL;
		ssize_t cookie = 0;
		area_info areaInfo;
		while (get_next_area_info(fDebugContext->team, &cookie, &areaInfo) == B_OK) {
			if (strcmp(areaInfo.name, RUNTIME_LOADER_DEBUG_AREA_NAME) == 0) {
				remoteDebugArea = (runtime_loader_debug_area*)areaInfo.address;
				break;
			}
		}

		if (remoteDebugArea) {
			TRACE(("SymbolLookup::Init(): found debug area, translating "
				"address...\n"));
		} else {
			TRACE(("SymbolLookup::Init(): Couldn't find debug area!\n"));
		}

		// translate the address
		try {
			if (remoteDebugArea != NULL) {
				fDebugArea = &Read(*remoteDebugArea);

				TRACE(("SymbolLookup::Init(): translated debug area is at: %p, "
					"loaded_images: %p\n", fDebugArea, fDebugArea->loaded_images));
			}
		} catch (Exception& exception) {
			// we can live without the debug area
		}
	}

	image_info imageInfo;
	if (fImageID < 0) {
		// create a list of the team's images
		int32 cookie = 0;
		while (get_next_image_info(fDebugContext->team, &cookie, &imageInfo) == B_OK) {
			error = _LoadImageInfo(imageInfo);
			if (error != B_OK)
				return error;
		}
	} else {
		error = get_image_info(fImageID, &imageInfo);
		if (error != B_OK)
			return error;

		error = _LoadImageInfo(imageInfo);
		if (error != B_OK)
			return error;
	}

	return B_OK;
}


status_t
SymbolLookup::LookupSymbolAddress(addr_t address, addr_t *_baseAddress,
	const char **_symbolName, size_t *_symbolNameLen, const char **_imageName,
	bool *_exactMatch) const
{
	TRACE(("SymbolLookup::LookupSymbolAddress(%p)\n", (void*)address));

	Image* image = _FindImageAtAddress(address);
	if (!image)
		return B_ENTRY_NOT_FOUND;

	if (_imageName != NULL)
		*_imageName = image->Name();

	const elf_sym* symbolFound = image->LookupSymbol(address, _baseAddress,
		_symbolName, _symbolNameLen, _exactMatch);

	TRACE(("SymbolLookup::LookupSymbolAddress(): done: symbol: %p, image name: "
		"%s, exact match: %d\n", symbolFound, image->Name(), _exactMatch ? *_exactMatch : -1));

	if (symbolFound != NULL)
		return B_OK;

	// symbol not found -- return the image itself

	if (_baseAddress)
		*_baseAddress = image->TextAddress();

	if (_imageName)
		*_imageName = image->Name();

	if (_symbolName)
		*_symbolName = NULL;

	if (_exactMatch)
		*_exactMatch = false;

	if (_symbolNameLen != NULL)
		*_symbolNameLen = 0;

	return B_OK;
}


status_t
SymbolLookup::InitSymbolIterator(image_id imageID,
	SymbolIterator& iterator) const
{
	TRACE(("SymbolLookup::InitSymbolIterator(): image ID: %" B_PRId32 "\n",
		imageID));

	// find the image
	iterator.image = _FindImageByID(imageID);

	// If that didn't work, find the loaded image.
	if (iterator.image == NULL) {
		TRACE(("SymbolLookup::InitSymbolIterator() done: image not "
			"found\n"));
		return B_ENTRY_NOT_FOUND;
	}

	iterator.currentIndex = -1;

	return B_OK;
}


status_t
SymbolLookup::InitSymbolIteratorByAddress(addr_t address,
	SymbolIterator& iterator) const
{
	TRACE(("SymbolLookup::InitSymbolIteratorByAddress(): base address: %#lx\n",
		address));

	// find the image
	iterator.image = _FindImageAtAddress(address);
	if (iterator.image == NULL) {
		TRACE(("SymbolLookup::InitSymbolIteratorByAddress() done: image "
			"not found\n"));
		return B_ENTRY_NOT_FOUND;
	}

	iterator.currentIndex = -1;

	return B_OK;
}


status_t
SymbolLookup::NextSymbol(SymbolIterator& iterator, const char** _symbolName,
	size_t* _symbolNameLen, addr_t* _symbolAddress, size_t* _symbolSize,
	int32* _symbolType) const
{
	return iterator.image->NextSymbol(iterator.currentIndex, _symbolName,
		_symbolNameLen, _symbolAddress, _symbolSize, _symbolType);
}


status_t
SymbolLookup::GetSymbol(image_id imageID, const char* name, int32 symbolType,
	void** _symbolLocation, size_t* _symbolSize, int32* _symbolType) const
{
	Image* image = _FindImageByID(imageID);
	if (image == NULL)
		return B_ENTRY_NOT_FOUND;

	return image->GetSymbol(name, symbolType, _symbolLocation, _symbolSize,
		_symbolType);
}


const image_t *
SymbolLookup::_FindLoadedImageAtAddress(addr_t address)
{
	TRACE(("SymbolLookup::_FindLoadedImageAtAddress(%p)\n", (void*)address));

	if (fDebugArea == NULL)
		return NULL;

	// iterate through the loaded images
	const image_t *_image = Read(fDebugArea->loaded_images->head);
	while (_image != NULL) {
		const image_t *image = &Read(*_image);
		_image = image->next;

		if (image->regions[0].vmstart <= address
			&& address < image->regions[0].vmstart + image->regions[0].size) {
			return image;
		}
	}

	return NULL;
}


const image_t*
SymbolLookup::_FindLoadedImageByID(image_id id)
{
	TRACE(("SymbolLookup::_FindLoadedImageByID(%" B_PRId32 ")\n", id));

	if (fDebugArea == NULL)
		return NULL;

	// iterate through the loaded images
	const image_t *_image = Read(fDebugArea->loaded_images->head);
	while (_image != NULL) {
		const image_t *image = &Read(*_image);
		_image = image->next;

		if (image->id == id)
			return image;
	}

	return NULL;
}


Image*
SymbolLookup::_FindImageAtAddress(addr_t address) const
{
	DoublyLinkedList<Image>::ConstIterator it = fImages.GetIterator();
	while (Image* image = it.Next()) {
		addr_t textAddress = image->TextAddress();
		if (address >= textAddress && address < textAddress + image->TextSize())
			return image;
	}

	return NULL;
}


Image*
SymbolLookup::_FindImageByID(image_id id) const
{
	DoublyLinkedList<Image>::ConstIterator it = fImages.GetIterator();
	while (Image* image = it.Next()) {
		if (image->ID() == id)
			return image;
	}

	return NULL;
}


size_t
SymbolLookup::_SymbolNameLen(const char* address) const
{
	Area* area = AreaForLocalAddress(address);
	if (area == NULL)
		return 0;

	return strnlen(address, (addr_t)area->LocalAddress() + area->Size()
		- (addr_t)address);
}


status_t
SymbolLookup::_LoadImageInfo(const image_info& imageInfo)
{
	status_t error = B_OK;

	Image* image;
	if (fDebugContext->team == B_SYSTEM_TEAM) {
		// kernel image
		KernelImage* kernelImage = new(std::nothrow) KernelImage;
		if (kernelImage == NULL)
			return B_NO_MEMORY;

		error = kernelImage->Init(imageInfo);
		image = kernelImage;
	} else if (!strcmp("commpage", imageInfo.name)) {
		// commpage image
		CommPageImage* commPageImage = new(std::nothrow) CommPageImage;
		if (commPageImage == NULL)
			return B_NO_MEMORY;

		error = commPageImage->Init(imageInfo);
		image = commPageImage;
	} else {
		// userland image -- try to load an image file
		ImageFile* imageFile = new(std::nothrow) ImageFile;
		if (imageFile == NULL)
			return B_NO_MEMORY;

		error = imageFile->Init(imageInfo);
		image = imageFile;
	}

	if (error != B_OK) {
		// initialization error -- fall back to the loaded image
		delete image;

		const image_t* loadedImage = _FindLoadedImageByID(imageInfo.id);
		if (loadedImage == NULL)
			return B_OK;

		image = new(std::nothrow) LoadedImage(this, imageInfo,
			loadedImage, Read(loadedImage->symhash[1]));
		if (image == NULL)
			return B_NO_MEMORY;

	}

	fImages.Add(image);

	return B_OK;
}

// #pragma mark - LoadedImage


SymbolLookup::LoadedImage::LoadedImage(SymbolLookup* symbolLookup,
	const image_info& info, const image_t* image, int32 symbolCount)
	:
	fSymbolLookup(symbolLookup),
	fImage(image),
	fSymbolCount(symbolCount),
	fLoadDelta(image->regions[0].delta)
{
	fInfo = info;
}


SymbolLookup::LoadedImage::~LoadedImage()
{
}


const elf_sym*
SymbolLookup::LoadedImage::LookupSymbol(addr_t address, addr_t* _baseAddress,
	const char** _symbolName, size_t *_symbolNameLen, bool *_exactMatch) const
{
	TRACE(("LoadedImage::LookupSymbol(): found image: ID: %" B_PRId32 ", text: "
		"address: %p, size: %ld\n", fImage->id,
		(void*)fImage->regions[0].vmstart, fImage->regions[0].size));

	// search the image for the symbol
	const elf_sym *symbolFound = NULL;
	addr_t deltaFound = INT_MAX;
	bool exactMatch = false;
	const char *symbolName = NULL;

	for (int32 i = 0; i < fSymbolCount; i++) {
		const elf_sym *symbol = &fSymbolLookup->Read(fImage->syms[i]);

		// The symbol table contains not only symbols referring to functions
		// and data symbols within the shared object, but also referenced
		// symbols of other shared objects, as well as section and file
		// references. We ignore everything but function and data symbols
		// that have an st_value != 0 (0 seems to be an indication for a
		// symbol defined elsewhere -- couldn't verify that in the specs
		// though).
		if ((symbol->Type() != STT_FUNC && symbol->Type() != STT_OBJECT)
			|| symbol->st_value == 0
			|| (symbol->st_value + symbol->st_size) > (size_t)fInfo.text_size) {
			continue;
		}

		// skip symbols starting after the given address
		addr_t symbolAddress = symbol->st_value + fLoadDelta;

		if (symbolAddress > address)
			continue;
		addr_t symbolDelta = address - symbolAddress;

		if (!symbolFound || symbolDelta < deltaFound) {
			symbolName = (const char*)fSymbolLookup->PrepareAddressNoThrow(
				SYMNAME(fImage, symbol), 1);
			if (symbolName == NULL)
				continue;

			deltaFound = symbolDelta;
			symbolFound = symbol;

			if (symbolDelta >= 0 && symbolDelta < symbol->st_size) {
				// exact match
				exactMatch = true;
				break;
			}
		}
	}

	TRACE(("LoadedImage::LookupSymbol(): done: symbol: %p, image name: "
		"%s, exact match: %d\n", symbolFound, fImage->name, exactMatch));

	if (symbolFound != NULL) {
		if (_baseAddress)
			*_baseAddress = symbolFound->st_value + fLoadDelta;
		if (_symbolName)
			*_symbolName = symbolName;
		if (_exactMatch)
			*_exactMatch = exactMatch;
		if (_symbolNameLen != NULL)
			*_symbolNameLen = fSymbolLookup->_SymbolNameLen(symbolName);
	}

	return symbolFound;
}


status_t
SymbolLookup::LoadedImage::NextSymbol(int32& iterator, const char** _symbolName,
	size_t* _symbolNameLen, addr_t* _symbolAddress, size_t* _symbolSize,
	int32* _symbolType) const
{
	while (true) {
		if (++iterator >= fSymbolCount)
			return B_ENTRY_NOT_FOUND;

		const elf_sym* symbol
			= &fSymbolLookup->Read(fImage->syms[iterator]);
		if ((symbol->Type() != STT_FUNC && symbol->Type() != STT_OBJECT)
			|| symbol->st_value == 0) {
			continue;
		}

		*_symbolName = (const char*)fSymbolLookup->PrepareAddressNoThrow(
			SYMNAME(fImage, symbol), 1);
		*_symbolNameLen = fSymbolLookup->_SymbolNameLen(*_symbolName);
		*_symbolAddress = symbol->st_value + fLoadDelta;
		*_symbolSize = symbol->st_size;
		*_symbolType = symbol->Type() == STT_FUNC ? B_SYMBOL_TYPE_TEXT
			: B_SYMBOL_TYPE_DATA;

		return B_OK;
	}
}