gdb_server: support gdb target description

* Add a parameter in .get_gdb_reg_list() to return different
  register lists as generating target description.
* Modify STRUCT REG to let gdb generate target description
  according to register information.

The modified structure of register is
struct reg {
        const char *name;
        uint32_t number;  /* for regnum="num" */
        struct reg_feature *feature;  /* for register group feature name */
        bool caller_save;  /* for save-restore="yes|no" */
        void *value;
        bool dirty;
        bool valid;
        bool exist;
        uint32_t size;
        struct reg_data_type *reg_data_type;  /* for type="type" */
        const char *group;  /* for group="general|float|vector" */
        void *arch_info;
        const struct reg_arch_type *type;
};

Change-Id: I2096b67adf94518ba0b8b23d8c6a9f64ad7932b8
Signed-off-by: Hsiangkai Wang <hsiangkai@gmail.com>
Reviewed-on: http://openocd.zylin.com/1382
Tested-by: jenkins
Reviewed-by: Franck Jullien <franck.jullien@gmail.com>
Reviewed-by: Spencer Oliver <spen@spen-soft.co.uk>
Reviewed-by: Andreas Fritiofson <andreas.fritiofson@gmail.com>
This commit is contained in:
Hsiangkai Wang
2013-05-07 21:43:35 +08:00
committed by Spencer Oliver
parent 9f2922aa7a
commit d979d78e97
16 changed files with 657 additions and 56 deletions

View File

@@ -14,6 +14,12 @@
* Copyright (C) ST-Ericsson SA 2011 *
* michel.jaouen@stericsson.com : smp minimum support *
* *
* Copyright (C) 2013 Andes Technology *
* Hsiangkai Wang <hkwang@andestech.com> *
* *
* Copyright (C) 2013 Franck Jullien *
* elec4fun@gmail.com *
* *
* 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 *
@@ -114,6 +120,11 @@ static int gdb_flash_program = 1;
*/
static int gdb_report_data_abort;
/* set if we are sending target descriptions to gdb
* via qXfer:features:read packet */
/* disabled by default */
static int gdb_use_target_description;
static int gdb_last_signal(struct target *target)
{
switch (target->debug_reason) {
@@ -968,7 +979,8 @@ static int gdb_get_registers_packet(struct connection *connection,
if ((target->rtos != NULL) && (ERROR_OK == rtos_get_gdb_reg_list(connection)))
return ERROR_OK;
retval = target_get_gdb_reg_list(target, &reg_list, &reg_list_size);
retval = target_get_gdb_reg_list(target, &reg_list, &reg_list_size,
REG_CLASS_GENERAL);
if (retval != ERROR_OK)
return gdb_error(connection, retval);
@@ -1027,7 +1039,8 @@ static int gdb_set_registers_packet(struct connection *connection,
return ERROR_SERVER_REMOTE_CLOSED;
}
retval = target_get_gdb_reg_list(target, &reg_list, &reg_list_size);
retval = target_get_gdb_reg_list(target, &reg_list, &reg_list_size,
REG_CLASS_GENERAL);
if (retval != ERROR_OK)
return gdb_error(connection, retval);
@@ -1072,7 +1085,8 @@ static int gdb_get_register_packet(struct connection *connection,
LOG_DEBUG("-");
#endif
retval = target_get_gdb_reg_list(target, &reg_list, &reg_list_size);
retval = target_get_gdb_reg_list(target, &reg_list, &reg_list_size,
REG_CLASS_ALL);
if (retval != ERROR_OK)
return gdb_error(connection, retval);
@@ -1109,7 +1123,8 @@ static int gdb_set_register_packet(struct connection *connection,
LOG_DEBUG("-");
retval = target_get_gdb_reg_list(target, &reg_list, &reg_list_size);
retval = target_get_gdb_reg_list(target, &reg_list, &reg_list_size,
REG_CLASS_ALL);
if (retval != ERROR_OK)
return gdb_error(connection, retval);
@@ -1670,6 +1685,331 @@ static int gdb_memory_map(struct connection *connection,
return ERROR_OK;
}
static const char *gdb_get_reg_type_name(enum reg_type type)
{
switch (type) {
case REG_TYPE_INT8:
return "int8";
case REG_TYPE_INT16:
return "int16";
case REG_TYPE_INT32:
return "int32";
case REG_TYPE_INT64:
return "int64";
case REG_TYPE_INT128:
return "int128";
case REG_TYPE_UINT8:
return "uint8";
case REG_TYPE_UINT16:
return "uint16";
case REG_TYPE_UINT32:
return "uint32";
case REG_TYPE_UINT64:
return "uint64";
case REG_TYPE_UINT128:
return "uint128";
case REG_TYPE_CODE_PTR:
return "code_ptr";
case REG_TYPE_DATA_PTR:
return "data_ptr";
case REG_TYPE_IEEE_SINGLE:
return "ieee_single";
case REG_TYPE_IEEE_DOUBLE:
return "ieee_double";
case REG_TYPE_ARCH_DEFINED:
return "int"; /* return arbitrary string to avoid compile warning. */
}
return "int"; /* "int" as default value */
}
static int gdb_generate_reg_type_description(struct target *target,
char **tdesc, int *pos, int *size, struct reg_data_type *type)
{
int retval = ERROR_OK;
if (type->type_class == REG_TYPE_CLASS_VECTOR) {
/* <vector id="id" type="type" count="count"/> */
xml_printf(&retval, tdesc, pos, size,
"<vector id=\"%s\" type=\"%s\" count=\"%d\"/>\n",
type->id, type->reg_type_vector->type->id,
type->reg_type_vector->count);
} else if (type->type_class == REG_TYPE_CLASS_UNION) {
/* <union id="id">
* <field name="name" type="type"/> ...
* </union> */
xml_printf(&retval, tdesc, pos, size,
"<union id=\"%s\">\n",
type->id);
struct reg_data_type_union_field *field;
field = type->reg_type_union->fields;
while (field != NULL) {
xml_printf(&retval, tdesc, pos, size,
"<field name=\"%s\" type=\"%s\"/>\n",
field->name, field->type->id);
field = field->next;
}
xml_printf(&retval, tdesc, pos, size,
"</union>\n");
} else if (type->type_class == REG_TYPE_CLASS_STRUCT) {
struct reg_data_type_struct_field *field;
field = type->reg_type_struct->fields;
if (field->use_bitfields) {
/* <struct id="id" size="size">
* <field name="name" start="start" end="end"/> ...
* </struct> */
xml_printf(&retval, tdesc, pos, size,
"<struct id=\"%s\" size=\"%d\">\n",
type->id, type->reg_type_struct->size);
while (field != NULL) {
xml_printf(&retval, tdesc, pos, size,
"<field name=\"%s\" start=\"%d\" end=\"%d\"/>\n",
field->name, field->bitfield->start,
field->bitfield->end);
field = field->next;
}
} else {
/* <struct id="id">
* <field name="name" type="type"/> ...
* </struct> */
xml_printf(&retval, tdesc, pos, size,
"<struct id=\"%s\">\n",
type->id);
while (field != NULL) {
xml_printf(&retval, tdesc, pos, size,
"<field name=\"%s\" type=\"%s\"/>\n",
field->name, field->type->id);
field = field->next;
}
}
xml_printf(&retval, tdesc, pos, size,
"</struct>\n");
} else if (type->type_class == REG_TYPE_CLASS_FLAGS) {
/* <flags id="id" size="size">
* <field name="name" start="start" end="end"/> ...
* </flags> */
xml_printf(&retval, tdesc, pos, size,
"<flags id=\"%s\" size=\"%d\">\n",
type->id, type->reg_type_flags->size);
struct reg_data_type_flags_field *field;
field = type->reg_type_flags->fields;
while (field != NULL) {
xml_printf(&retval, tdesc, pos, size,
"<field name=\"%s\" start=\"%d\" end=\"%d\"/>\n",
field->name, field->bitfield->start, field->bitfield->end);
field = field->next;
}
xml_printf(&retval, tdesc, pos, size,
"</flags>\n");
}
return ERROR_OK;
}
/* Get a list of available target registers features. feature_list must
* be freed by caller.
*/
int get_reg_features_list(struct target *target, char **feature_list[], int *feature_list_size,
struct reg **reg_list, int reg_list_size)
{
int tbl_sz = 0;
/* Start with only one element */
*feature_list = calloc(1, sizeof(char *));
for (int i = 0; i < reg_list_size; i++) {
if (reg_list[i]->exist == false)
continue;
if ((reg_list[i]->feature->name != NULL)
&& (strcmp(reg_list[i]->feature->name, ""))) {
/* We found a feature, check if the feature is already in the
* table. If not, allocate a new entry for the table and
* put the new feature in it.
*/
for (int j = 0; j < (tbl_sz + 1); j++) {
if (!((*feature_list)[j])) {
(*feature_list)[tbl_sz++] = strdup(reg_list[i]->feature->name);
*feature_list = realloc(*feature_list, sizeof(char *) * (tbl_sz + 1));
(*feature_list)[tbl_sz] = NULL;
break;
} else {
if (!strcmp((*feature_list)[j], reg_list[i]->feature->name))
break;
}
}
}
}
if (feature_list_size)
*feature_list_size = tbl_sz;
return ERROR_OK;
}
static int gdb_generate_target_description(struct target *target, char **tdesc)
{
int retval = ERROR_OK;
struct reg **reg_list;
int reg_list_size;
int pos = 0;
int size = 0;
xml_printf(&retval, tdesc, &pos, &size,
"<?xml version=\"1.0\"?>\n"
"<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n"
"<target version=\"1.0\">\n");
retval = target_get_gdb_reg_list(target, &reg_list,
&reg_list_size, REG_CLASS_ALL);
if (retval != ERROR_OK) {
LOG_ERROR("get register list failed");
return ERROR_FAIL;
}
if (reg_list_size <= 0)
return ERROR_FAIL;
char **features = NULL;
/* Get a list of available target registers features */
retval = get_reg_features_list(target, &features, NULL, reg_list, reg_list_size);
if (retval != ERROR_OK) {
LOG_ERROR("Can't get the registers feature list");
return ERROR_FAIL;
}
/* If we found some features associated with registers, create sections */
int current_feature = 0;
/* generate target description according to register list */
if (features != NULL) {
while (features[current_feature]) {
xml_printf(&retval, tdesc, &pos, &size,
"<feature name=\"%s\">\n",
features[current_feature]);
int i;
for (i = 0; i < reg_list_size; i++) {
if (reg_list[i]->exist == false)
continue;
if (strcmp(reg_list[i]->feature->name, features[current_feature]))
continue;
const char *type_str;
if (reg_list[i]->reg_data_type != NULL) {
if (reg_list[i]->reg_data_type->type == REG_TYPE_ARCH_DEFINED) {
/* generate <type... first, if there are architecture-defined types. */
gdb_generate_reg_type_description(target, tdesc, &pos, &size,
reg_list[i]->reg_data_type);
type_str = reg_list[i]->reg_data_type->id;
} else {
/* predefined type */
type_str = gdb_get_reg_type_name(
reg_list[i]->reg_data_type->type);
}
} else {
/* Default type is "int" */
type_str = "int";
}
xml_printf(&retval, tdesc, &pos, &size,
"<reg name=\"%s\"", reg_list[i]->name);
xml_printf(&retval, tdesc, &pos, &size,
" bitsize=\"%d\"", reg_list[i]->size);
xml_printf(&retval, tdesc, &pos, &size,
" regnum=\"%d\"", reg_list[i]->number);
if (reg_list[i]->caller_save)
xml_printf(&retval, tdesc, &pos, &size,
" save-restore=\"yes\"");
else
xml_printf(&retval, tdesc, &pos, &size,
" save-restore=\"no\"");
xml_printf(&retval, tdesc, &pos, &size,
" type=\"%s\"", type_str);
if (reg_list[i]->group != NULL)
xml_printf(&retval, tdesc, &pos, &size,
" group=\"%s\"", reg_list[i]->group);
xml_printf(&retval, tdesc, &pos, &size,
"/>\n");
}
xml_printf(&retval, tdesc, &pos, &size,
"</feature>\n");
current_feature++;
}
}
xml_printf(&retval, tdesc, &pos, &size,
"</target>\n");
if (reg_list != NULL)
free(reg_list);
if (features != NULL)
free(features);
return ERROR_OK;
}
static int gdb_get_target_description_chunk(struct target *target, char **chunk,
int32_t offset, uint32_t length)
{
static char *tdesc;
static uint32_t tdesc_length;
if (tdesc == NULL) {
gdb_generate_target_description(target, &tdesc);
tdesc_length = strlen(tdesc);
}
char transfer_type;
if (length < (tdesc_length - offset))
transfer_type = 'm';
else
transfer_type = 'l';
*chunk = malloc(length + 2);
(*chunk)[0] = transfer_type;
if (transfer_type == 'm') {
strncpy((*chunk) + 1, tdesc + offset, length);
(*chunk)[1 + length] = '\0';
} else {
strncpy((*chunk) + 1, tdesc + offset, tdesc_length - offset);
(*chunk)[1 + (tdesc_length - offset)] = '\0';
/* After gdb-server sends out last chunk, invalidate tdesc. */
free(tdesc);
tdesc = NULL;
tdesc_length = 0;
}
return ERROR_OK;
}
static int gdb_query_packet(struct connection *connection,
char *packet, int packet_size)
{
@@ -1744,9 +2084,10 @@ static int gdb_query_packet(struct connection *connection,
&buffer,
&pos,
&size,
"PacketSize=%x;qXfer:memory-map:read%c;qXfer:features:read-;QStartNoAckMode+",
"PacketSize=%x;qXfer:memory-map:read%c;qXfer:features:read%c;QStartNoAckMode+",
(GDB_BUFFER_SIZE - 1),
((gdb_use_memory_map == 1) && (flash_get_bank_count() > 0)) ? '+' : '-');
((gdb_use_memory_map == 1) && (flash_get_bank_count() > 0)) ? '+' : '-',
(gdb_use_target_description == 1) ? '+' : '-');
if (retval != ERROR_OK) {
gdb_send_error(connection, 01);
@@ -1762,8 +2103,6 @@ static int gdb_query_packet(struct connection *connection,
return gdb_memory_map(connection, packet, packet_size);
else if (strncmp(packet, "qXfer:features:read:", 20) == 0) {
char *xml = NULL;
int size = 0;
int pos = 0;
int retval = ERROR_OK;
int offset;
@@ -1778,17 +2117,12 @@ static int gdb_query_packet(struct connection *connection,
return ERROR_OK;
}
if (strcmp(annex, "target.xml") != 0) {
gdb_send_error(connection, 01);
return ERROR_OK;
}
xml_printf(&retval,
&xml,
&pos,
&size, \
"l < target version=\"1.0\">\n < architecture > arm</architecture>\n</target>\n");
/* Target should prepare correct target description for annex.
* The first character of returned xml is 'm' or 'l'. 'm' for
* there are *more* chunks to transfer. 'l' for it is the *last*
* chunk of target description.
*/
retval = gdb_get_target_description_chunk(target, &xml, offset, length);
if (retval != ERROR_OK) {
gdb_error(connection, retval);
return retval;
@@ -2372,6 +2706,54 @@ COMMAND_HANDLER(handle_gdb_breakpoint_override_command)
return ERROR_OK;
}
COMMAND_HANDLER(handle_gdb_target_description_command)
{
if (CMD_ARGC != 1)
return ERROR_COMMAND_SYNTAX_ERROR;
COMMAND_PARSE_ENABLE(CMD_ARGV[0], gdb_use_target_description);
return ERROR_OK;
}
COMMAND_HANDLER(handle_gdb_save_tdesc_command)
{
static char *tdesc;
static uint32_t tdesc_length;
struct target *target = get_current_target(CMD_CTX);
char *tdesc_filename;
if (tdesc == NULL) {
gdb_generate_target_description(target, &tdesc);
tdesc_length = strlen(tdesc);
}
struct fileio fileio;
size_t size_written;
tdesc_filename = malloc(strlen(target_type_name(target)) + 5);
sprintf(tdesc_filename, "%s.xml", target_type_name(target));
int retval = fileio_open(&fileio, tdesc_filename, FILEIO_WRITE, FILEIO_TEXT);
free(tdesc_filename);
if (retval != ERROR_OK) {
LOG_WARNING("Can't open %s for writing", tdesc_filename);
return ERROR_FAIL;
}
retval = fileio_write(&fileio, tdesc_length, tdesc, &size_written);
fileio_close(&fileio);
if (retval != ERROR_OK) {
LOG_WARNING("Error while writing the tdesc file");
return ERROR_FAIL;
}
return ERROR_OK;
}
static const struct command_registration gdb_command_handlers[] = {
{
.name = "gdb_sync",
@@ -2424,6 +2806,19 @@ static const struct command_registration gdb_command_handlers[] = {
"to be used by gdb 'break' commands.",
.usage = "('hard'|'soft'|'disable')"
},
{
.name = "gdb_target_description",
.handler = handle_gdb_target_description_command,
.mode = COMMAND_CONFIG,
.help = "enable or disable target description",
.usage = "('enable'|'disable')"
},
{
.name = "gdb_save_tdesc",
.handler = handle_gdb_save_tdesc_command,
.mode = COMMAND_EXEC,
.help = "Save the target description file",
},
COMMAND_REGISTRATION_DONE
};