⛏️ index : haiku.git

/*
 * Copyright 2005-2007, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 *
 * Copyright 2002, Manuel J. Petit. All rights reserved.
 * Distributed under the terms of the NewOS License.
 */

/** Contains the code to interface with a remote GDB */

#include "gdb.h"

#include <string.h>
#include <stdarg.h>
#include <stdio.h>

#include <ByteOrder.h>

#include <arch/debug.h>
#include <arch/debug_console.h>
#include <debug.h>
#include <elf.h>
#include <elf_priv.h>
#include <smp.h>
#include <vm/vm.h>


enum { INIT = 0, CMDREAD, CKSUM1, CKSUM2, WAITACK, QUIT, GDBSTATES };


static char sCommand[512];
static int sCommandIndex;
static int sCheckSum;

static char sReply[512];
static char sSafeMemory[512];


// utility functions


static int
parse_nibble(int input)
{
	int nibble = 0xff;

	if (input >= '0' && input <= '9')
		nibble = input - '0';

	if (input >= 'A' && input <= 'F')
		nibble = 0x0a + input - 'A';

	if (input >= 'a' && input <= 'f')
		nibble = 0x0a + input - 'a';

	return nibble;
}


//	#pragma mark - GDB protocol


static void
gdb_ack(void)
{
	arch_debug_serial_putchar('+');
}


static void
gdb_nak(void)
{
	arch_debug_serial_putchar('-');
}


static void
gdb_resend_reply(void)
{
	arch_debug_serial_puts(sReply);
}


static void
gdb_reply(char const* format, ...)
{
	int i;
	int len;
	int sum;
	va_list args;

	va_start(args, format);
	sReply[0] = '$';
	vsprintf(sReply + 1, format, args);
	va_end(args);

	len = strlen(sReply);
	sum = 0;
	for (i = 1; i < len; i++) {
		sum += sReply[i];
	}
	sum %= 256;

	sprintf(sReply + len, "#%02x", sum);

	gdb_resend_reply();
}


static void
gdb_regreply()
{
	sReply[0] = '$';

	// get registers (architecture specific)
	ssize_t bytesWritten = arch_debug_gdb_get_registers(sReply + 1,
		sizeof(sReply) - 1);
	if (bytesWritten < 0) {
		gdb_reply("E01");
		return;
	}

	// add 1 for the leading '$'
	bytesWritten++;

	// compute check sum
	int sum = 0;
	for (int32 i = 1; i < bytesWritten; i++)
		sum += sReply[i];
	sum %= 256;

	// print check sum
	int result = snprintf(sReply + bytesWritten, sizeof(sReply) - bytesWritten,
		"#%02x", sum);
	if (result >= (ssize_t)sizeof(sReply) - bytesWritten) {
		gdb_reply("E01");
		return;
	}

	gdb_resend_reply();
}


static void
gdb_memreply(char const* bytes, int numbytes)
{
	int i;
	int len;
	int sum;

	sReply[0] = '$';
	for (i = 0; i < numbytes; i++)
		sprintf(sReply + 1 + 2 * i, "%02x", (uint8)bytes[i]);

	len = strlen(sReply);
	sum = 0;
	for (i = 1; i < len; i++)
		sum += sReply[i];
	sum %= 256;

	sprintf(sReply + len, "#%02x", sum);

	gdb_resend_reply();
}


//	#pragma mark - checksum verification


static int
gdb_verify_checksum(void)
{
	int i;
	int len;
	int sum;

	len = strlen(sCommand);
	sum = 0;
	for (i = 0; i < len; i++)
		sum += sCommand[i];
	sum %= 256;

	return (sum == sCheckSum) ? 1 : 0;
}


//	#pragma mark - command parsing


static int
gdb_parse_command(void)
{
	if (!gdb_verify_checksum()) {
		gdb_nak();
		return INIT;
	} else
		gdb_ack();

	switch (sCommand[0]) {
		case '?':
			// command '?' is used for retrieving the signal
			// that stopped the program. Fully implemeting
			// this command requires help from the debugger,
			// by now we just fake a SIGKILL
			gdb_reply("S09");	/* SIGKILL = 9 */
			break;

		case 'H':
			// Command H (actually Hct) is used to select
			// the current thread (-1 meaning all threads)
			// We just fake we recognize the the command
			// and send an 'OK' response.
			gdb_reply("OK");
			break;

		case 'q':
		{
			// query commands

			if (strcmp(sCommand + 1, "Supported") == 0) {
				// get the supported features
				gdb_reply("");
			} else if (strcmp(sCommand + 1, "Offsets") == 0) {
				// get the segment offsets
				elf_image_info* kernelImage = elf_get_kernel_image();
				gdb_reply("Text=%lx;Data=%lx;Bss=%lx",
					kernelImage->text_region.delta,
					kernelImage->data_region.delta,
					kernelImage->data_region.delta);
			} else
				gdb_reply("");

			break;
		}

		case 'c':
			// continue at address
			// TODO: Parse the address and resume there!
			return QUIT;

		case 'g':
			gdb_regreply();
			break;

		case 'G':
			// write registers
			// TODO: Implement!
			gdb_reply("E01");
			break;


		case 'm':
		{
			char* ptr;
			addr_t address;
			size_t len;

			// The 'm' command has the form mAAA,LLL
			// where AAA is the address and LLL is the
			// number of bytes.
			ptr = sCommand + 1;
			address = 0;
			len = 0;
			while (ptr && *ptr && (*ptr != ',')) {
				address <<= 4;
				address += parse_nibble(*ptr);
				ptr += 1;
			}
			if (*ptr == ',')
				ptr += 1;

			while (ptr && *ptr) {
				len <<= 4;
				len += parse_nibble(*ptr);
				ptr += 1;
			}

			if (len > 128)
				len = 128;

			// We cannot directly access the requested memory
			// for gdb may be trying to access an stray pointer
			// We copy the memory to a safe buffer using
			// the bulletproof debug_memcpy().
			if (debug_memcpy(B_CURRENT_TEAM, sSafeMemory, (char*)address, len)
					< 0) {
				gdb_reply("E02");
			} else
				gdb_memreply(sSafeMemory, len);

			break;
		}

		case 'D':
			// detach
			return QUIT;

		case 'k':
			// Command 'k' actual semantics is 'kill the damn thing'.
			// However gdb sends that command when you disconnect
			// from a debug session. I guess that 'kill' for the
			// kernel would map to reboot... however that's a
			// a very mean thing to do, instead we just quit
			// the gdb state machine and fallback to the regular
			// kernel debugger command prompt.
			return QUIT;

		case 's':
			// "step" -- resume (?) at address
			// TODO: Implement!
			gdb_reply("E01");
			break;

		default:
			gdb_reply("");
			break;
	}

	return WAITACK;
}


//	#pragma mark - protocol state machine


static int
gdb_init_handler(int input)
{
	switch (input) {
		case '$':
			memset(sCommand, 0, sizeof(sCommand));
			sCommandIndex = 0;
			return CMDREAD;

		default:
#if 0
			gdb_nak();
#else
			// looks to me like we should send
			// a NAK here but it kinda works
			// better if we just gobble all
			// junk chars silently
#endif
			return INIT;
	}
}


static int
gdb_cmdread_handler(int input)
{
	switch (input) {
		case '#':
			return CKSUM1;

		default:
			sCommand[sCommandIndex] = input;
			sCommandIndex += 1;
			return CMDREAD;
	}
}


static int
gdb_cksum1_handler(int input)
{
	int nibble = parse_nibble(input);

	if (nibble == 0xff) {
#if 0
		gdb_nak();
		return INIT;
#else
		// looks to me like we should send
		// a NAK here but it kinda works
		// better if we just gobble all
		// junk chars silently
#endif
	}

	sCheckSum = nibble << 4;

	return CKSUM2;
}


static int
gdb_cksum2_handler(int input)
{
	int nibble = parse_nibble(input);

	if (nibble == 0xff) {
#if 0
		gdb_nak();
		return INIT;
#else
		// looks to me like we should send
		// a NAK here but it kinda works
		// better if we just gobble all
		// junk chars silently
#endif
	}

	sCheckSum += nibble;

	return gdb_parse_command();
}


static int
gdb_waitack_handler(int input)
{
	switch (input) {
		case '+':
			return INIT;
		case '-':
			gdb_resend_reply();
			return WAITACK;

		default:
			// looks like gdb and us are out of sync,
			// send a NAK and retry from INIT state.
			gdb_nak();
			return INIT;
	}
}


static int
gdb_quit_handler(int input)
{
	(void)(input);

	// actually we should never be here
	return QUIT;
}


static int (*dispatch_table[GDBSTATES])(int) = {
	&gdb_init_handler,
	&gdb_cmdread_handler,
	&gdb_cksum1_handler,
	&gdb_cksum2_handler,
	&gdb_waitack_handler,
	&gdb_quit_handler
};


static int
gdb_state_dispatch(int curr, int input)
{
	if (curr < INIT || curr >= GDBSTATES)
		return QUIT;

	return dispatch_table[curr](input);
}


static int
gdb_state_machine(void)
{
	int state = INIT;
	int c;

	while (state != QUIT) {
		c = arch_debug_serial_getchar();
		state = gdb_state_dispatch(state, c);
	}

	return 0;
}


//	#pragma mark -


int
cmd_gdb(int argc, char** argv)
{
	(void)(argc);
	(void)(argv);

	return gdb_state_machine();
}