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

#include <new>

#include <string.h>

#include <AutoDeleter.h>
#include <debug_support.h>

#include "arch_debug_support.h"
#include "Image.h"
#include "SymbolLookup.h"


using std::nothrow;


struct debug_symbol_lookup_context {
	SymbolLookup*	lookup;
};

struct debug_symbol_iterator : BPrivate::Debug::SymbolIterator {
	bool	ownsImage;

	debug_symbol_iterator()
		:
		ownsImage(false)
	{
	}

	~debug_symbol_iterator()
	{
		if (ownsImage)
			delete image;
	}
};


// init_debug_context
status_t
init_debug_context(debug_context *context, team_id team, port_id nubPort)
{
	if (!context || team < 0 || nubPort < 0)
		return B_BAD_VALUE;

	context->team = team;
	context->nub_port = nubPort;

	// create the reply port
	context->reply_port = create_port(1, "debug reply port");
	if (context->reply_port < 0)
		return context->reply_port;

	return B_OK;
}

// destroy_debug_context
void
destroy_debug_context(debug_context *context)
{
	if (context) {
		if (context->reply_port >= 0)
			delete_port(context->reply_port);

		context->team = -1;
		context->nub_port = -1;
		context->reply_port = -1;
	}
}

// send_debug_message
status_t
send_debug_message(debug_context *context, int32 messageCode,
	const void *message, int32 messageSize, void *reply, int32 replySize)
{
	if (!context)
		return B_BAD_VALUE;

	// send message
	while (true) {
		status_t result = write_port(context->nub_port, messageCode, message,
			messageSize);
		if (result == B_OK)
			break;
		if (result != B_INTERRUPTED)
			return result;
	}

	if (!reply)
		return B_OK;

	// read reply
	while (true) {
		int32 code;
		ssize_t bytesRead = read_port(context->reply_port, &code, reply,
			replySize);
		if (bytesRead > 0)
			return B_OK;
		if (bytesRead != B_INTERRUPTED)
			return bytesRead;
	}
}

// debug_read_memory_partial
ssize_t
debug_read_memory_partial(debug_context *context, const void *address,
	void *buffer, size_t size)
{
	if (!context)
		return B_BAD_VALUE;

	if (size == 0)
		return 0;
	if (size > B_MAX_READ_WRITE_MEMORY_SIZE)
		size = B_MAX_READ_WRITE_MEMORY_SIZE;

	// prepare the message
	debug_nub_read_memory message;
	message.reply_port = context->reply_port;
	message.address = (void*)address;
	message.size = size;

	// send the message
	debug_nub_read_memory_reply reply;
	status_t error = send_debug_message(context, B_DEBUG_MESSAGE_READ_MEMORY,
		&message, sizeof(message), &reply, sizeof(reply));

	if (error != B_OK)
		return error;
	if (reply.error != B_OK)
		return reply.error;

	// copy the read data
	memcpy(buffer, reply.data, reply.size);
	return reply.size;
}

// debug_read_memory
ssize_t
debug_read_memory(debug_context *context, const void *_address, void *_buffer,
	size_t size)
{
	const char *address = (const char *)_address;
	char *buffer = (char*)_buffer;

	// check parameters
	if (!context || !address || !buffer)
		return B_BAD_VALUE;
	if (size == 0)
		return 0;

	// read as long as we can read data
	ssize_t sumRead = 0;
	while (size > 0) {
		ssize_t bytesRead = debug_read_memory_partial(context, address, buffer,
			size);
		if (bytesRead < 0) {
			if (sumRead > 0)
				return sumRead;
			return bytesRead;
		}

		address += bytesRead;
		buffer += bytesRead;
		sumRead += bytesRead;
		size -= bytesRead;
	}

	return sumRead;
}

// debug_read_string
ssize_t
debug_read_string(debug_context *context, const void *_address, char *buffer,
	size_t size)
{
	const char *address = (const char *)_address;

	// check parameters
	if (!context || !address || !buffer || size == 0)
		return B_BAD_VALUE;

	// read as long as we can read data
	ssize_t sumRead = 0;
	while (size > 0) {
		ssize_t bytesRead = debug_read_memory_partial(context, address, buffer,
			size);
		if (bytesRead < 0) {
			// always null-terminate what we have (even, if it is an empty
			// string) and be done
			*buffer = '\0';
			return (sumRead > 0 ? sumRead : bytesRead);
		}

		int chunkSize = strnlen(buffer, bytesRead);
		if (chunkSize < bytesRead) {
			// we found a terminating null
			sumRead += chunkSize;
			return sumRead;
		}

		address += bytesRead;
		buffer += bytesRead;
		sumRead += bytesRead;
		size -= bytesRead;
	}

	// We filled the complete buffer without encountering a terminating null
	// replace the last char. But nevertheless return the full size to indicate
	// that the buffer was too small.
	buffer[-1] = '\0';

	return sumRead;
}

// debug_write_memory_partial
ssize_t
debug_write_memory_partial(debug_context *context, const void *address,
	void *buffer, size_t size)
{
	if (!context)
		return B_BAD_VALUE;

	if (size == 0)
		return 0;
	if (size > B_MAX_READ_WRITE_MEMORY_SIZE)
		size = B_MAX_READ_WRITE_MEMORY_SIZE;

	// prepare the message
	debug_nub_write_memory message;
	message.reply_port = context->reply_port;
	message.address = (void*)address;
	message.size = size;
	memcpy(message.data, buffer, size);

	// send the message
	debug_nub_write_memory_reply reply;
	status_t error = send_debug_message(context, B_DEBUG_MESSAGE_WRITE_MEMORY,
		&message, sizeof(message), &reply, sizeof(reply));

	if (error != B_OK)
		return error;
	if (reply.error != B_OK)
		return reply.error;

	return reply.size;
}

// debug_write_memory
ssize_t
debug_write_memory(debug_context *context, const void *_address, void *_buffer,
	size_t size)
{
	const char *address = (const char *)_address;
	char *buffer = (char*)_buffer;

	// check parameters
	if (!context || !address || !buffer)
		return B_BAD_VALUE;
	if (size == 0)
		return 0;

	ssize_t sumWritten = 0;
	while (size > 0) {
		ssize_t bytesWritten = debug_write_memory_partial(context, address, buffer,
			size);
		if (bytesWritten < 0) {
			if (sumWritten > 0)
				return sumWritten;
			return bytesWritten;
		}

		address += bytesWritten;
		buffer += bytesWritten;
		sumWritten += bytesWritten;
		size -= bytesWritten;
	}

	return sumWritten;
}

// debug_get_cpu_state
status_t
debug_get_cpu_state(debug_context *context, thread_id thread,
	debug_debugger_message *messageCode, debug_cpu_state *cpuState)
{
	if (!context || !cpuState)
		return B_BAD_VALUE;

	// prepare message
	debug_nub_get_cpu_state message;
	message.reply_port = context->reply_port;
	message.thread = thread;

	// send message
	debug_nub_get_cpu_state_reply reply;
	status_t error = send_debug_message(context, B_DEBUG_MESSAGE_GET_CPU_STATE,
		&message, sizeof(message), &reply, sizeof(reply));
	if (error == B_OK)
		error = reply.error;

	// get state
	if (error == B_OK) {
		*cpuState = reply.cpu_state;
		if (messageCode)
			*messageCode = reply.message;
	}

	return error;
}


// #pragma mark -

// debug_get_instruction_pointer
status_t
debug_get_instruction_pointer(debug_context *context, thread_id thread,
	void **ip, void **stackFrameAddress)
{
	if (!context || !ip || !stackFrameAddress)
		return B_BAD_VALUE;

	return arch_debug_get_instruction_pointer(context, thread, ip,
		stackFrameAddress);
}

// debug_get_stack_frame
status_t
debug_get_stack_frame(debug_context *context, void *stackFrameAddress,
	debug_stack_frame_info *stackFrameInfo)
{
	if (!context || !stackFrameAddress || !stackFrameInfo)
		return B_BAD_VALUE;

	return arch_debug_get_stack_frame(context, stackFrameAddress,
		stackFrameInfo);
}


// #pragma mark -

// debug_create_symbol_lookup_context
status_t
debug_create_symbol_lookup_context(team_id team, image_id image,
	debug_symbol_lookup_context **_lookupContext)
{
	if (team < 0 || !_lookupContext)
		return B_BAD_VALUE;

	// create the lookup context
	debug_symbol_lookup_context *lookupContext
		= new(std::nothrow) debug_symbol_lookup_context;
	if (lookupContext == NULL)
		return B_NO_MEMORY;
	ObjectDeleter<debug_symbol_lookup_context> contextDeleter(lookupContext);

	// create and init symbol lookup
	SymbolLookup *lookup = new(std::nothrow) SymbolLookup(team, image);
	if (lookup == NULL)
		return B_NO_MEMORY;
	ObjectDeleter<SymbolLookup> lookupDeleter(lookup);

	try {
		status_t error = lookup->Init();
		if (error != B_OK)
			return error;
	} catch (BPrivate::Debug::Exception& exception) {
		return exception.Error();
	}

	// everything went fine: return the result
	lookupContext->lookup = lookup;
	*_lookupContext = lookupContext;
	contextDeleter.Detach();
	lookupDeleter.Detach();

	return B_OK;
}

// debug_delete_symbol_lookup_context
void
debug_delete_symbol_lookup_context(debug_symbol_lookup_context *lookupContext)
{
	if (lookupContext) {
		delete lookupContext->lookup;
		delete lookupContext;
	}
}


// debug_get_symbol
status_t
debug_get_symbol(debug_symbol_lookup_context* lookupContext, image_id image,
	const char* name, int32 symbolType, void** _symbolLocation,
	size_t* _symbolSize, int32* _symbolType)
{
	if (!lookupContext || !lookupContext->lookup)
		return B_BAD_VALUE;
	SymbolLookup* lookup = lookupContext->lookup;

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


// debug_lookup_symbol_address
status_t
debug_lookup_symbol_address(debug_symbol_lookup_context *lookupContext,
	const void *address, void **baseAddress, char *symbolName,
	int32 symbolNameSize, char *imageName, int32 imageNameSize,
	bool *exactMatch)
{
	if (!lookupContext || !lookupContext->lookup)
		return B_BAD_VALUE;
	SymbolLookup *lookup = lookupContext->lookup;

	// find the symbol
	addr_t _baseAddress;
	const char *_symbolName;
	size_t _symbolNameLen;
	const char *_imageName;
	try {
		status_t error = lookup->LookupSymbolAddress((addr_t)address,
			&_baseAddress, &_symbolName, &_symbolNameLen, &_imageName,
			exactMatch);
		if (error != B_OK)
			return error;
	} catch (BPrivate::Debug::Exception& exception) {
		return exception.Error();
	}

	// translate/copy the results
	if (baseAddress)
		*baseAddress = (void*)_baseAddress;

	if (symbolName && symbolNameSize > 0) {
		if (_symbolName && _symbolNameLen > 0) {
			strlcpy(symbolName, _symbolName,
				min_c((size_t)symbolNameSize, _symbolNameLen + 1));
		} else
			symbolName[0] = '\0';
	}

	if (imageName) {
		if (imageNameSize > B_PATH_NAME_LENGTH)
			imageNameSize = B_PATH_NAME_LENGTH;
		strlcpy(imageName, _imageName, imageNameSize);
	}

	return B_OK;
}


status_t
debug_create_image_symbol_iterator(debug_symbol_lookup_context* lookupContext,
	image_id imageID, debug_symbol_iterator** _iterator)
{
	if (!lookupContext || !lookupContext->lookup)
		return B_BAD_VALUE;
	SymbolLookup *lookup = lookupContext->lookup;

	debug_symbol_iterator* iterator = new(std::nothrow) debug_symbol_iterator;
	if (iterator == NULL)
		return B_NO_MEMORY;

	status_t error;
	try {
		error = lookup->InitSymbolIterator(imageID, *iterator);
	} catch (BPrivate::Debug::Exception& exception) {
		error = exception.Error();
	}

	// Work-around for a runtime loader problem. A freshly fork()ed child does
	// still have image_t structures with the parent's image ID's, so we
	// wouldn't find the image in this case.
	if (error != B_OK) {
		// Get the image info and re-try looking with the text base address.
		// Note, that we can't easily check whether the image is part of the
		// target team at all (there's no image_info::team, we'd have to
		// iterate through all images).
		image_info imageInfo;
		error = get_image_info(imageID, &imageInfo);
		if (error == B_OK) {
			try {
				error = lookup->InitSymbolIteratorByAddress(
					(addr_t)imageInfo.text, *iterator);
			} catch (BPrivate::Debug::Exception& exception) {
				error = exception.Error();
			}
		}
	}

	if (error != B_OK) {
		delete iterator;
		return error;
	}

	*_iterator = iterator;
	return B_OK;
}


status_t
debug_create_file_symbol_iterator(const char* path,
	debug_symbol_iterator** _iterator)
{
	if (path == NULL)
		return B_BAD_VALUE;

	// create the iterator
	debug_symbol_iterator* iterator = new(std::nothrow) debug_symbol_iterator;
	if (iterator == NULL)
		return B_NO_MEMORY;
	ObjectDeleter<debug_symbol_iterator> iteratorDeleter(iterator);

	// create the image file
	ImageFile* imageFile = new(std::nothrow) ImageFile;
	if (imageFile == NULL)
		return B_NO_MEMORY;

	// init the iterator
	iterator->image = imageFile;
	iterator->ownsImage = true;
	iterator->currentIndex = -1;

	// init the image file
	status_t error = imageFile->Init(path);
	if (error != B_OK)
		return error;

	iteratorDeleter.Detach();
	*_iterator = iterator;

	return B_OK;
}


void
debug_delete_symbol_iterator(debug_symbol_iterator* iterator)
{
	delete iterator;
}


// debug_next_image_symbol
status_t
debug_next_image_symbol(debug_symbol_iterator* iterator, char* nameBuffer,
	size_t nameBufferLength, int32* _symbolType, void** _symbolLocation,
	size_t* _symbolSize)
{
	if (iterator == NULL || iterator->image == NULL)
		return B_BAD_VALUE;

	const char* symbolName;
	size_t symbolNameLen;
	addr_t symbolLocation;

	try {
		status_t error = iterator->image->NextSymbol(iterator->currentIndex,
			&symbolName, &symbolNameLen, &symbolLocation, _symbolSize,
			_symbolType);
		if (error != B_OK)
			return error;
	} catch (BPrivate::Debug::Exception& exception) {
		return exception.Error();
	}

	*_symbolLocation = (void*)symbolLocation;

	if (symbolName != NULL && symbolNameLen > 0) {
		strlcpy(nameBuffer, symbolName,
			min_c(nameBufferLength, symbolNameLen + 1));
	} else
		nameBuffer[0] = '\0';

	return B_OK;
}


status_t
debug_get_symbol_iterator_image_info(debug_symbol_iterator* iterator,
	image_info* info)
{
	if (iterator == NULL || iterator->image == NULL || info == NULL)
		return B_BAD_VALUE;

	*info = iterator->image->Info();
	return B_OK;
}