Support qXfer:spaces:read and qXfer:spaces:write. These gdbstub features allow GDB to access target's hardware registers.
Signed-off-by: Alexander Barabash <alexander_barab...@mentor.com> --- Makefile.target | 1 + gdbstub.c | 176 ++++++++++++++++++++++- xfer-spaces.c | 419 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ xfer-spaces.h | 74 ++++++++++ 4 files changed, 667 insertions(+), 3 deletions(-) create mode 100644 xfer-spaces.c create mode 100644 xfer-spaces.h diff --git a/Makefile.target b/Makefile.target index 5bfa4960..361ef2b 100644 --- a/Makefile.target +++ b/Makefile.target @@ -109,6 +109,7 @@ CONFIG_NO_GET_MEMORY_MAPPING = $(if $(subst n,,$(CONFIG_HAVE_GET_MEMORY_MAPPING) CONFIG_NO_CORE_DUMP = $(if $(subst n,,$(CONFIG_HAVE_CORE_DUMP)),n,y) obj-y += arch_init.o cpus.o monitor.o gdbstub.o balloon.o ioport.o +obj-y += xfer-spaces.o obj-y += hw/ obj-$(CONFIG_KVM) += kvm-all.o obj-$(CONFIG_NO_KVM) += kvm-stub.o diff --git a/gdbstub.c b/gdbstub.c index a8dd437..f1c2208 100644 --- a/gdbstub.c +++ b/gdbstub.c @@ -18,6 +18,7 @@ */ #include "config.h" #include "qemu-common.h" +#include "xfer-spaces.h" #ifdef CONFIG_USER_ONLY #include <stdlib.h> #include <stdio.h> @@ -1798,6 +1799,35 @@ static int memtox(char *buf, const char *mem, int len) return p - buf; } +/* Decode data using the encoding for 'x' packets. */ +static int xtomem(uint8_t *mem, const char *buf, int len) +{ + uint8_t *p = mem; + char c; + bool escaped = false; + + while (len--) { + c = *(buf++); + if (escaped) { + escaped = false; + *(p++) = c ^ 0x20; + } else { + if (c == '}') { + escaped = true; + } else { + *(p++) = c; + } + } + } + + if (escaped) { + fprintf(stderr, "Unmatched escape character in target response.\n"); + return 0; + } + + return p - mem; +} + static const char *get_feature_xml(const char *p, const char **newp) { size_t len; @@ -1832,6 +1862,9 @@ static const char *get_feature_xml(const char *p, const char **newp) } return target_xml; } + if (strncmp(p, "xfer-spaces.xml", len) == 0) { + return xfer_spaces_get_xml(); + } for (i = 0; ; i++) { name = xml_builtin[i][0]; if (!name || (strncmp(name, p, len) == 0 && strlen(name) == len)) @@ -2058,7 +2091,8 @@ static CPUArchState *find_cpu(uint32_t thread_id) return NULL; } -static int gdb_handle_packet(GDBState *s, const char *line_buf) +static int gdb_handle_packet(GDBState *s, const char *line_buf, + const char *line_buf_end) { CPUArchState *env; const char *p; @@ -2424,7 +2458,9 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) if (strncmp(p, "Supported", 9) == 0) { snprintf(buf, sizeof(buf), "PacketSize=%x", MAX_PACKET_LENGTH); #ifdef GDB_CORE_XML - pstrcat(buf, sizeof(buf), ";qXfer:features:read+"); + pstrcat(buf, sizeof(buf), + ";qXfer:features:read+" + ";qXfer:spaces:read+;qXfer:spaces:write+"); #endif put_packet(s, buf); break; @@ -2468,6 +2504,139 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) put_packet_binary(s, buf, len + 1); break; } + + if (strncmp(p, "Xfer:spaces:read:", strlen("Xfer:spaces:read:")) == 0) { + bool request_wellformed = false; + const char *space_name; + uint64_t offset; + uint64_t length; + char **colon_tokens; + const char *offset_and_length; + char **offset_and_length_tokens = NULL; + char *offset_string; + char *length_string; + + p += strlen("Xfer:spaces:read:"); + colon_tokens = g_strsplit(p, ":", 2); + do { + space_name = colon_tokens[0]; + if (space_name == NULL) { + break; + } + offset_and_length = colon_tokens[1]; + if (offset_and_length == NULL) { + break; + } + offset_and_length_tokens = g_strsplit(offset_and_length, + ",", 2); + offset_string = offset_and_length_tokens[0]; + if (offset_string == NULL) { + break; + } + length_string = offset_and_length_tokens[1]; + if (length_string == NULL) { + break; + } + + offset = g_ascii_strtoull(offset_string, &offset_string, 16); + if (offset > G_MAXUINT32) { + break; + } + if (*offset_string != '\0') { + break; + } + + length = g_ascii_strtoull(length_string, &length_string, 16); + if (length > MAX_PACKET_LENGTH) { + break; + } + if (*length_string != '\0') { + break; + } + + request_wellformed = true; + } while (false); + + if (request_wellformed) { + if (xfer_spaces_read(space_name, (unsigned)offset, + mem_buf, (unsigned)length)) { + buf[0] = 'm'; + memtox(buf + 1, (const char *)mem_buf, length); + put_packet_binary(s, buf, length + 1); + } else { + put_packet(s, ""); + } + } else { + put_packet(s, "E00"); + } + + if (offset_and_length_tokens != NULL) { + g_strfreev(offset_and_length_tokens); + } + g_strfreev(colon_tokens); + break; + } + + if (strncmp(p, "Xfer:spaces:write:", + strlen("Xfer:spaces:write:")) == 0) { + bool request_wellformed = false; + const char *space_name; + uint64_t offset; + unsigned length; + char **colon_tokens; + char *offset_string; + + p += strlen("Xfer:spaces:write:"); + colon_tokens = g_strsplit(p, ":", 3); + do { + space_name = colon_tokens[0]; + if (space_name == NULL) { + break; + } + p += strlen(space_name) + strlen(":"); + + offset_string = colon_tokens[1]; + if (offset_string == NULL) { + break; + } + p += strlen(offset_string) + strlen(":"); + + /* + * The third token is the data to write, + * which may contain NULL-characters. + */ + + offset = g_ascii_strtoull(offset_string, &offset_string, 16); + if (offset > G_MAXUINT32) { + break; + } + if (*offset_string != '\0') { + break; + } + + length = xtomem(mem_buf, p, line_buf_end - p); + if (length == 0) { + break; + } + + request_wellformed = true; + } while (false); + + if (request_wellformed) { + if (xfer_spaces_write(space_name, (unsigned)offset, + mem_buf, length)) { + sprintf(buf, "%02X", length); + put_packet(s, buf); + } else { + put_packet(s, ""); + } + } else { + put_packet(s, "E00"); + } + + g_strfreev(colon_tokens); + break; + } #endif /* Unrecognised 'q' command. */ goto unknown_command; @@ -2701,7 +2870,8 @@ static void gdb_read_byte(GDBState *s, int ch) } else { reply = '+'; put_buffer(s, &reply, 1); - s->state = gdb_handle_packet(s, s->line_buf); + s->state = gdb_handle_packet(s, s->line_buf, + s->line_buf + s->line_buf_index); } break; default: diff --git a/xfer-spaces.c b/xfer-spaces.c new file mode 100644 index 0000000..702e98b --- /dev/null +++ b/xfer-spaces.c @@ -0,0 +1,419 @@ +/* + * GDB Stub xfer-spaces support code. + * + * Copyright (c) 2012 Mentor Graphics Corp. + * + * Author: Alex Rozenman <alex_rozen...@mentor.com> + * Maintainer: Alexander Barabash <alexander_barab...@mentor.com> + * + * 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. + */ + +#include "xfer-spaces.h" +#include <stdlib.h> +#include <string.h> + +typedef struct XferSpace XferSpace; +typedef struct XferTreeNode XferTreeNode; +typedef struct XferRecord XferRecord; + +static void xfer_spaces_dump_xml(GString *xml); + +static XferRecord *get_record(const char *space_name, + unsigned offset, + unsigned length); +static XferSpace *get_or_create_space(const char *name); +static const char *get_space_name(XferSpace *space); +static void space_create_map(XferSpace *space); +static void space_create_tree(XferSpace *space); +static void clear_space(XferSpace *space); + +static void xml_dump_node_and_siblings(GString *xml, + XferTreeNode *node, + unsigned indent); +static void xml_dump_node(GString *xml, + XferTreeNode *node, + unsigned indent); +static void merge_record(XferTreeNode *node, XferRecord *record); +static XferTreeNode *get_or_create_child_node(XferTreeNode *node, + const char *name); +static const char *get_node_name(XferTreeNode *node); +static XferTreeNode *alloc_node(void); +static void dealloc_node_and_siblings(XferTreeNode *node); +static void clear_node(XferTreeNode *node); + +static XferRecord *alloc_record(XferObject *object); + +static void xml_print_indent(GString *xml, unsigned indent); +static void dealloc_g_string(GString *s); + +struct XferSpace { + XferSpace *next; + GString *name; + XferRecord *list; + XferTreeNode *tree; + XferRecord **map; + unsigned map_size; +}; + +struct XferTreeNode { + XferTreeNode *sibling; + XferTreeNode *child; + XferRecord *record; + GString *name; +}; + +struct XferRecord { + XferRecord *next; + XferObject *object; + unsigned offset; +}; + +static XferSpace *xfer_spaces; + +#define NAME_SEPARATOR "." +#define INITIAL_OFFSET 0x10 + +void xfer_spaces_declare_object(const char *space_name, XferObject *object) +{ + XferSpace *space; + XferRecord *record; + + space = get_or_create_space(space_name); + clear_space(space); + + /* Add to the initial list. */ + record = alloc_record(object); + record->next = space->list; + space->list = record; +} + +const char *xfer_spaces_get_xml(void) +{ + static GString *xml; + bool need_dump = false; + XferSpace *space; + + if (xml == NULL) { + xml = g_string_sized_new(4 * 1024); + } + + for (space = xfer_spaces; space; space = space->next) { + if (space->map == NULL) { + space_create_map(space); + need_dump = true; + } + if (space->tree == NULL) { + space_create_tree(space); + need_dump = true; + } + } + + if (need_dump) { + g_string_truncate(xml, 0); + xfer_spaces_dump_xml(xml); + } + + return xml->str; +} + +bool xfer_spaces_read(const char *space_name, + unsigned offset, + uint8_t *data, + unsigned length) +{ + XferRecord *record = get_record(space_name, offset, length); + if (record == NULL) { + return false; + } + if (record->object->read == NULL) { + return false; + } + return record->object->read(record->object, data); +} + +/* Write operation on object(s) addressed by their space and offset. */ +bool xfer_spaces_write(const char *space_name, + unsigned offset, + const uint8_t *data, + unsigned length) +{ + XferRecord *record = get_record(space_name, offset, length); + if (record == NULL) { + return false; + } + if (record->object->write == NULL) { + return false; + } + return record->object->write(record->object, data); +} + +static void xfer_spaces_dump_xml(GString *xml) +{ + XferSpace *space; + const char *space_name; + g_string_append(xml, "<?xml version=\"1.0\"?>\n"); + g_string_append(xml, "<!DOCTYPE feature SYSTEM \"gdb-target.dtd\">\n"); + g_string_append(xml, "<feature name=\"org.gnu.gdb.xfer-spaces\">\n"); + for (space = xfer_spaces; space; space = space->next) { + space_name = get_space_name(space); + g_string_append_printf(xml, "<space annex=\"%s\" name=\"%s\">\n", + space_name, space_name); + xml_dump_node_and_siblings(xml, space->tree, 0); + g_string_append_printf(xml, "</space>\n"); + } + g_string_append(xml, "</feature>\n"); +} + +static XferRecord *get_record(const char *space_name, + unsigned offset, + unsigned length) { + XferRecord *record; + XferSpace *space = get_or_create_space(space_name); + + if (space->map == NULL) { + space_create_map(space); + } + + if (offset + length > space->map_size) { + return NULL; + } + + record = space->map[offset]; + if (!record) { + return NULL; + } + + if (record->object->get_size(record->object) != length) { + return NULL; + } + + return record; +} + +static XferSpace *get_or_create_space(const char *name) +{ + XferSpace *space; + + for (space = xfer_spaces; space; space = xfer_spaces->next) { + if (strcmp(name, get_space_name(space)) == 0) { + return space; + } + } + + space = (XferSpace *)g_malloc0(sizeof(XferSpace)); + space->next = xfer_spaces; + space->name = g_string_new(name); + xfer_spaces = space; + return space; +} + +static const char *get_space_name(XferSpace *space) +{ + if (space->name != NULL) { + return space->name->str; + } else { + return NULL; + } +} + +static void space_create_map(XferSpace *space) +{ + XferRecord *record; + unsigned offset = INITIAL_OFFSET; + + /* Set offsets and merge into the tree. */ + for (record = space->list; record; record = record->next) { + record->offset = offset; + offset += record->object->get_size(record->object); + } + + /* Create the map. */ + space->map_size = offset; + space->map = + (XferRecord **)g_malloc0(space->map_size * sizeof(*space->map)); + for (record = space->list; record; record = record->next) { + space->map[record->offset] = record; + } +} + +static void space_create_tree(XferSpace *space) +{ + XferRecord *record; + + /* Create the tree root. */ + space->tree = alloc_node(); + + /* Merge into the tree. */ + for (record = space->list; record; record = record->next) { + merge_record(space->tree, record); + } +} + +static void clear_space(XferSpace *space) +{ + if (space != NULL) { + return; + } + + g_free(space->map); + space->map = 0; + space->map_size = 0; + + dealloc_node_and_siblings(space->tree); + space->tree = 0; +} + +static void xml_dump_node_and_siblings(GString *xml, + XferTreeNode *node, + unsigned indent) +{ + for (; node; node = node->sibling) { + xml_dump_node(xml, node, indent); + } +} + +static void xml_dump_node(GString *xml, + XferTreeNode *node, + unsigned indent) +{ + const char *node_name; + + xml_print_indent(xml, indent); + node_name = get_node_name(node); + if (node->record) { + unsigned data_size = + node->record->object->get_size(node->record->object); + g_string_append_printf(xml, + "<reg " + "name=\"%s\" " + "offset=\"%d\" " + "bitsize=\"%d\"/>\n", + node_name, + node->record->offset, + data_size * 8); + } else { + if (node_name != NULL) { + g_string_append_printf(xml, "<group name=\"%s\">\n", node_name); + xml_dump_node_and_siblings(xml, node->child, indent + 2); + xml_print_indent(xml, indent); + g_string_append_printf(xml, "</group>\n"); + } else { + xml_dump_node_and_siblings(xml, node->child, indent); + } + } +} + +static void merge_record(XferTreeNode *node, XferRecord *record) +{ + char *child_name; + gchar **tokens; + gchar **tokens_pointer; + GString *path; + + path = g_string_sized_new(32); + record->object->get_name(record->object, path); + + tokens = tokens_pointer = g_strsplit(path->str, NAME_SEPARATOR, 0); + + for (child_name = *tokens_pointer; child_name; ++tokens_pointer) { + if (*child_name == '\0') { + continue; + } + node = get_or_create_child_node(node, child_name); + } + + g_strfreev(tokens); + dealloc_g_string(path); + + node->record = record; +} + +static XferTreeNode *get_or_create_child_node(XferTreeNode *node, + const char *name) +{ + XferTreeNode *child; + for (child = node->child; child; child = child->sibling) { + if (g_strcmp0(name, get_node_name(child)) == 0) { + return child; + } + } + child = alloc_node(); + child->name = g_string_new(name); + child->sibling = node->child; + node->child = child; + return child; +} + +static const char *get_node_name(XferTreeNode *node) +{ + if (node->name != NULL) { + return node->name->str; + } else { + return NULL; + } +} + +static XferTreeNode *alloc_node(void) +{ + return (XferTreeNode *)g_malloc0(sizeof(XferTreeNode)); +} + +static void dealloc_node_and_siblings(XferTreeNode *node) +{ + while (node) { + XferTreeNode *prev_node; + clear_node(node); + prev_node = node; + node = node->sibling; + g_free(prev_node); + } +} + +static void clear_node(XferTreeNode *node) +{ + if (node == NULL) { + return; + } + dealloc_g_string(node->name); + dealloc_node_and_siblings(node->child); + g_free(node->child); +} + +static XferRecord *alloc_record(XferObject *object) +{ + XferRecord *record = (XferRecord *)g_malloc0(sizeof(XferRecord)); + record->object = object; + return record; +} + +static void xml_print_indent(GString *xml, unsigned indent) +{ + unsigned i; + for (i = 0; i < indent; ++i) { + g_string_append(xml, " "); + } +} + +static void dealloc_g_string(GString *s) +{ + if (s != NULL) { + g_string_free(s, true); + } +} diff --git a/xfer-spaces.h b/xfer-spaces.h new file mode 100644 index 0000000..a640362 --- /dev/null +++ b/xfer-spaces.h @@ -0,0 +1,74 @@ + +/* + * GDB Stub xfer-spaces support code. + * + * Copyright (c) 2012 Mentor Graphics Corp. + * + * Author: Alex Rozenman <alex_rozen...@mentor.com> + * Maintainer: Alexander Barabash <alexander_barab...@mentor.com> + * + * 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. + */ + +#ifndef XFER_SPACES_H +#define XFER_SPACES_H + +#include <glib.h> +#include <stdbool.h> +#include <stdint.h> + +typedef struct XferObject XferObject; + +struct XferObject { + + /* This function shall return the name of the object. The name is + considered to be a hierarchical separated by dots. */ + void (*get_name)(XferObject *this, GString *output); + + /* This function shall return the data size (bytes) of + this object. */ + unsigned (*get_size)(XferObject *this); + + /* Read the data contained in the object. Return boolean success status. */ + bool (*read)(XferObject *this, uint8_t *buf); + + /* Write the data into the object. Return boolean success status. */ + bool (*write)(XferObject *this, const uint8_t *buf); +}; + +/* Declare an object in a xfer:space. */ +void xfer_spaces_declare_object(const char *space_name, + XferObject *object); + +/* Dump xfer:space XML description. */ +const char *xfer_spaces_get_xml(void); + +/* Read operation on object(s) addressed by their space and offset. */ +bool xfer_spaces_read(const char *space_name, + unsigned offset, + uint8_t *data, + unsigned length); + +/* Write operation on object(s) addressed by their space and offset. */ +bool xfer_spaces_write(const char *space_name, + unsigned offset, + const uint8_t *data, + unsigned length); + +#endif /* XFER_SPACES_H */ -- 1.7.9.5