summaryrefslogtreecommitdiff
path: root/payloads/libpayload/gdb
diff options
context:
space:
mode:
Diffstat (limited to 'payloads/libpayload/gdb')
-rw-r--r--payloads/libpayload/gdb/Makefile.inc20
-rw-r--r--payloads/libpayload/gdb/commands.c101
-rw-r--r--payloads/libpayload/gdb/stub.c124
-rw-r--r--payloads/libpayload/gdb/transport.c235
4 files changed, 480 insertions, 0 deletions
diff --git a/payloads/libpayload/gdb/Makefile.inc b/payloads/libpayload/gdb/Makefile.inc
new file mode 100644
index 0000000000..cacd0d02f3
--- /dev/null
+++ b/payloads/libpayload/gdb/Makefile.inc
@@ -0,0 +1,20 @@
+##
+## Copyright 2014 Google Inc.
+##
+## This program is free software; you can redistribute it and/or
+## modify it under the terms of the GNU General Public License as
+## published by the Free Software Foundation; either version 2 of
+## the License, or (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but without any warranty; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+libgdb-y += commands.c
+libgdb-y += stub.c
+libgdb-y += transport.c
diff --git a/payloads/libpayload/gdb/commands.c b/payloads/libpayload/gdb/commands.c
new file mode 100644
index 0000000000..5137dfd9f5
--- /dev/null
+++ b/payloads/libpayload/gdb/commands.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but without any warranty; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <arch/cache.h>
+#include <gdb.h>
+#include <libpayload.h>
+
+static void gdb_get_last_signal(struct gdb_message *command,
+ int offset, struct gdb_message *reply)
+{
+ gdb_message_add_string(reply, "S");
+ gdb_message_encode_bytes(reply, &gdb_state.signal, 1);
+}
+
+static void gdb_read_general_registers(struct gdb_message *command,
+ int offset, struct gdb_message *reply)
+{
+ gdb_arch_encode_regs(reply);
+}
+
+static void gdb_write_general_registers(struct gdb_message *command,
+ int offset, struct gdb_message *reply)
+{
+ gdb_arch_decode_regs(offset, command);
+ gdb_message_add_string(reply, "OK");
+}
+
+static void gdb_read_memory(struct gdb_message *command,
+ int offset, struct gdb_message *reply)
+{
+ int tok = gdb_message_tokenize(command, &offset);
+ uintptr_t addr = gdb_message_decode_int(command, tok, offset - 1 - tok);
+ size_t length = gdb_message_decode_int(command, offset,
+ command->used - offset);
+
+ gdb_message_encode_bytes(reply, (void *)addr, length);
+}
+
+static void gdb_write_memory(struct gdb_message *command,
+ int offset, struct gdb_message *reply)
+{
+ int tok = gdb_message_tokenize(command, &offset);
+ uintptr_t addr = gdb_message_decode_int(command, tok, offset - 1 - tok);
+ tok = gdb_message_tokenize(command, &offset);
+ size_t length = gdb_message_decode_int(command, tok, offset - 1 - tok);
+
+ die_if(length * 2 != command->used - offset, "Invalid length field in "
+ "GDB command: %.*s", command->used, command->buf);
+
+ gdb_message_decode_bytes(command, offset, (void *)addr, length);
+ cache_sync_instructions();
+ gdb_message_add_string(reply, "OK");
+}
+
+static void gdb_continue(struct gdb_message *command,
+ int offset, struct gdb_message *reply)
+{
+ /* Disable single step if it's still on. */
+ gdb_arch_set_single_step(0);
+
+ /* No need to support the extension that passes in new EIP/PC. */
+ if (command->used > offset)
+ gdb_message_add_string(reply, "E00");
+ else
+ gdb_state.resumed = 1;
+}
+
+static void gdb_single_step(struct gdb_message *command,
+ int offset, struct gdb_message *reply)
+{
+ if (command->used > offset || gdb_arch_set_single_step(1))
+ gdb_message_add_string(reply, "E00");
+ else
+ gdb_state.resumed = 1;
+}
+
+struct gdb_command gdb_commands[] = {
+ { "?", &gdb_get_last_signal },
+ { "g", &gdb_read_general_registers },
+ { "G", &gdb_write_general_registers },
+ { "m", &gdb_read_memory },
+ { "M", &gdb_write_memory },
+ { "c", &gdb_continue },
+ { "s", &gdb_single_step }
+};
+const int gdb_command_count = ARRAY_SIZE(gdb_commands);
diff --git a/payloads/libpayload/gdb/stub.c b/payloads/libpayload/gdb/stub.c
new file mode 100644
index 0000000000..73afa70c47
--- /dev/null
+++ b/payloads/libpayload/gdb/stub.c
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but without any warranty; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <gdb.h>
+#include <libpayload.h>
+
+struct gdb_state gdb_state;
+
+static u8 reply_buf[2048];
+static u8 command_buf[2048];
+
+static struct gdb_message command = {
+ .buf = command_buf,
+ .used = 0,
+ .size = sizeof(command_buf),
+};
+static struct gdb_message reply = {
+ .buf = reply_buf,
+ .used = 0,
+ .size = sizeof(reply_buf),
+};
+
+void gdb_command_loop(u8 signal)
+{
+ if (gdb_state.resumed) {
+ /* We were just running. Send a stop reply. */
+ reply.used = 0;
+ gdb_message_add_string(&reply, "S");
+ gdb_message_encode_bytes(&reply, &signal, 1);
+ gdb_send_reply(&reply);
+
+ }
+ gdb_state.signal = signal;
+ gdb_state.resumed = 0;
+ gdb_state.connected = 1;
+
+ while (1) {
+ int i;
+
+ gdb_get_command(&command);
+
+ reply.used = 0;
+ for (i = 0; i < gdb_command_count; i++) {
+ int clen = strlen(gdb_commands[i].str);
+ if (!strncmp(gdb_commands[i].str, (char *)command.buf,
+ MIN(clen, command.used))) {
+ gdb_commands[i].handler(&command, clen, &reply);
+ break;
+ }
+ }
+
+ /* If we're resuming, we won't send a reply until we stop. */
+ if (gdb_state.resumed)
+ return;
+
+ gdb_send_reply(&reply);
+ }
+}
+
+static void gdb_output_write(const void *buffer, size_t count)
+{
+ if (!gdb_state.resumed) {
+ /* Must be a die_if() in GDB (or a bug), so bail out and die. */
+ gdb_exit(-1);
+ video_console_init();
+ puts("GDB died, redirecting its last words to the screen:\n");
+ console_write(buffer, count);
+ } else {
+ reply.used = 0;
+ reply.buf[reply.used++] = 'O';
+ gdb_message_encode_bytes(&reply, buffer, count);
+ gdb_send_reply(&reply);
+ }
+}
+
+static struct console_output_driver gdb_output_driver = {
+ .write = &gdb_output_write
+};
+
+static void gdb_init(void)
+{
+ printf("Ready for GDB connection.\n");
+ gdb_transport_init();
+ gdb_arch_init();
+ console_add_output_driver(&gdb_output_driver);
+}
+
+void gdb_enter(void)
+{
+ if (!gdb_state.connected)
+ gdb_init();
+ gdb_arch_enter();
+}
+
+void gdb_exit(s8 exit_status)
+{
+ if (!gdb_state.connected)
+ return;
+
+ reply.used = 0;
+ gdb_message_add_string(&reply, "W");
+ gdb_message_encode_bytes(&reply, &exit_status, 1);
+ gdb_send_reply(&reply);
+
+ console_remove_output_driver(&gdb_output_write);
+ gdb_transport_teardown();
+ gdb_state.connected = 0;
+ printf("Detached from GDB connection.\n");
+}
diff --git a/payloads/libpayload/gdb/transport.c b/payloads/libpayload/gdb/transport.c
new file mode 100644
index 0000000000..596ceb5c6d
--- /dev/null
+++ b/payloads/libpayload/gdb/transport.c
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but without any warranty; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <gdb.h>
+#include <libpayload.h>
+
+static const int timeout_us = 100 * 1000;
+static const char output_overrun[] = "GDB output buffer overrun (try "
+ "increasing reply.size)!\n";
+static const char input_underrun[] = "GDB input message truncated (bug or "
+ "communication problem)?\n";
+
+/* Serial-specific glue code... add more transport layers here when desired. */
+
+static void gdb_raw_putchar(u8 c)
+{
+ serial_putchar(c);
+}
+
+static int gdb_raw_getchar(void)
+{
+ u64 start = timer_us(0);
+
+ while (!serial_havechar())
+ if (timer_us(start) > timeout_us)
+ return -1;
+
+ return serial_getchar();
+}
+
+void gdb_transport_init(void)
+{
+ console_remove_output_driver(serial_putchar);
+}
+
+void gdb_transport_teardown(void)
+{
+ serial_console_init();
+}
+
+/* Hex digit character <-> number conversion (illegal chars undefined!). */
+
+static s8 from_hex(unsigned char c)
+{
+ static const s8 values[] = {
+ -1, 10, 11, 12, 13, 14, 15, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, -1, -1, -1, -1, -1, -1,
+ };
+
+ return values[c & 0x1f];
+}
+
+static char to_hex(u8 v)
+{
+ static const char digits[] = "0123456789abcdef";
+
+ return digits[v & 0xf];
+}
+
+/* Message encode/decode functions */
+
+void gdb_message_encode_bytes(struct gdb_message *message, const void *data,
+ int length)
+{
+ const u8 *bytes = data;
+ die_if(message->used + length * 2 > message->size, output_overrun);
+ while (length--) {
+ message->buf[message->used++] = to_hex(*bytes >> 4);
+ message->buf[message->used++] = to_hex(*bytes & 0xf);
+ bytes++;
+ }
+}
+
+void gdb_message_decode_bytes(const struct gdb_message *message, int offset,
+ void *data, int length)
+{
+ u8 *bytes = data;
+ die_if(offset + 2 * length > message->used, "Decode overrun in GDB "
+ "message: %.*s", message->used, message->buf);
+ while (length--) {
+ *bytes = from_hex(message->buf[offset++]) << 4;
+ *bytes += from_hex(message->buf[offset++]);
+ bytes++;
+ }
+}
+
+void gdb_message_encode_zero_bytes(struct gdb_message *message, int length)
+{
+ die_if(message->used + length * 2 > message->size, output_overrun);
+ memset(message->buf + message->used, '0', length * 2);
+ message->used += length * 2;
+}
+
+void gdb_message_add_string(struct gdb_message *message, const char *string)
+{
+ message->used += strlcpy((char *)message->buf + message->used,
+ string, message->size - message->used);
+
+ /* Check >= instead of > to account for strlcpy's trailing '\0'. */
+ die_if(message->used >= message->size, output_overrun);
+}
+
+void gdb_message_encode_int(struct gdb_message *message, uintptr_t val)
+{
+ int length = sizeof(uintptr_t) * 2 - __builtin_clz(val) / 4;
+ die_if(message->used + length > message->size, output_overrun);
+ while (length--)
+ message->buf[message->used++] =
+ to_hex((val >> length * 4) & 0xf);
+}
+
+uintptr_t gdb_message_decode_int(const struct gdb_message *message, int offset,
+ int length)
+{
+ uintptr_t val = 0;
+
+ die_if(length > sizeof(uintptr_t) * 2, "GDB decoding invalid number: "
+ "%.*s", message->used, message->buf);
+
+ while (length--) {
+ val <<= 4;
+ val |= from_hex(message->buf[offset++]);
+ }
+
+ return val;
+}
+
+/* Like strtok/strsep: writes back offset argument, returns original offset. */
+int gdb_message_tokenize(const struct gdb_message *message, int *offset)
+{
+ int token = *offset;
+ while (!strchr(",;:", message->buf[(*offset)++]))
+ die_if(*offset >= message->used, "Undelimited token in GDB "
+ "message at offset %d: %.*s",
+ token, message->used, message->buf);
+ return token;
+}
+
+/* High-level send/receive functions. */
+
+void gdb_get_command(struct gdb_message *command)
+{
+ enum command_state {
+ STATE_WAITING,
+ STATE_COMMAND,
+ STATE_CHECKSUM0,
+ STATE_CHECKSUM1,
+ };
+
+ u8 checksum = 0;
+ u8 running_checksum = 0;
+ enum command_state state = STATE_WAITING;
+
+ while (1) {
+ int c = gdb_raw_getchar();
+ if (c < 0) {
+ /*
+ * Timeout waiting for a byte. Reset the
+ * state machine.
+ */
+ state = STATE_WAITING;
+ continue;
+ }
+
+ switch (state) {
+ case STATE_WAITING:
+ if (c == '$') {
+ running_checksum = 0;
+ command->used = 0;
+ state = STATE_COMMAND;
+ }
+ break;
+ case STATE_COMMAND:
+ if (c == '#') {
+ state = STATE_CHECKSUM0;
+ break;
+ }
+ die_if(command->used >= command->size, "GDB input buf"
+ "fer overrun (try increasing command.size)!\n");
+ command->buf[command->used++] = c;
+ running_checksum += c;
+ break;
+ case STATE_CHECKSUM0:
+ checksum = from_hex(c) << 4;
+ state = STATE_CHECKSUM1;
+ break;
+ case STATE_CHECKSUM1:
+ checksum += from_hex(c);
+ if (running_checksum == checksum) {
+ gdb_raw_putchar('+');
+ return;
+ } else {
+ state = STATE_WAITING;
+ gdb_raw_putchar('-');
+ }
+ break;
+ }
+ }
+}
+
+void gdb_send_reply(const struct gdb_message *reply)
+{
+ int i;
+ int retries = 1 * 1000 * 1000 / timeout_us;
+ u8 checksum = 0;
+
+ for (i = 0; i < reply->used; i++)
+ checksum += reply->buf[i];
+
+ do {
+ gdb_raw_putchar('$');
+ for (i = 0; i < reply->used; i++)
+ gdb_raw_putchar(reply->buf[i]);
+ gdb_raw_putchar('#');
+ gdb_raw_putchar(to_hex(checksum >> 4));
+ gdb_raw_putchar(to_hex(checksum & 0xf));
+ } while (gdb_raw_getchar() != '+' && retries--);
+}