Compare commits

...

2 Commits

Author SHA1 Message Date
Mikaela Szekely 3c38492268 misc: move ARRAY_SIZE macro into general.h 2 years ago
Mikaela Szekely 9c1747f43d target/cortex: dynamically generate tdesc strings to save code size 2 years ago
  1. 1
      src/Makefile
  2. 2
      src/include/general.h
  3. 137
      src/target/cortexa.c
  4. 256
      src/target/cortexm.c
  5. 46
      src/target/gdb_reg.c
  6. 58
      src/target/gdb_reg.h

1
src/Makefile

@ -30,6 +30,7 @@ SRC = \
gdb_main.c \
gdb_hostio.c \
gdb_packet.c \
gdb_reg.c \
hex_utils.c \
jtag_devs.c \
jtag_scan.c \

2
src/include/general.h

@ -42,6 +42,8 @@
#include "platform.h"
#include "platform_support.h"
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
extern uint32_t delay_cnt;
enum BMP_DEBUG {

137
src/target/cortexa.c

@ -31,8 +31,11 @@
#include "exception.h"
#include "adiv5.h"
#include "target.h"
#include "gdb_reg.h"
#include "target_internal.h"
#include <assert.h>
static const char cortexa_driver_str[] = "ARM Cortex-A";
static bool cortexa_attach(target *t);
@ -154,8 +157,46 @@ struct cortexa_priv {
/* Thumb mode bit in CPSR */
#define CPSR_THUMB (1 << 5)
/* GDB register map / target description */
static const char tdesc_cortex_a[] =
/**
* Fields for Cortex-A special purpose registers, used in the generation of GDB's target description XML.
* The general purpose registers r0-r12 and the vector floating point registers d0-d15 all follow a very
* regular format, so we only need to store fields for the special purpose registers.
* The arrays for each SPR field have the same order as each other, making each of them as pseduo
* 'associative array'.
*/
// Strings for the names of the Cortex-A's special purpose registers.
static const char *cortex_a_spr_names[] = {
"sp",
"lr",
"pc",
"cpsr"
};
// The "type" field for each Cortex-A special purpose register.
static const gdb_reg_type_e cortex_a_spr_types[] = {
GDB_TYPE_DATA_PTR, // sp
GDB_TYPE_CODE_PTR, // lr
GDB_TYPE_CODE_PTR, // pc
GDB_TYPE_UNSPECIFIED // cpsr
};
static_assert(ARRAY_SIZE(cortex_a_spr_types) == ARRAY_SIZE(cortex_a_spr_names),
"SPR array length mixmatch! SPR type array should have the same length as SPR name array."
);
// Creates the target description XML string for a Cortex-A. Like snprintf(), this function
// will write no more than max_len and returns the amount of bytes written. Or, if max_len is 0,
// then this function will return the amount of bytes that _would_ be necessary to create this
// string.
//
// This function is hand-optimized to decrease string duplication and thus code size, making it
// Unfortunately much less readable than the string literal it is equivalent to.
//
// The string it creates is XML-equivalent to the following:
/*
"<?xml version=\"1.0\"?>"
"<!DOCTYPE feature SYSTEM \"gdb-target.dtd\">"
"<target>"
@ -199,6 +240,90 @@ static const char tdesc_cortex_a[] =
" <reg name=\"d15\" bitsize=\"64\" type=\"float\"/>"
" </feature>"
"</target>";
*/
// Returns the amount of characters written to the buffer.
static size_t create_tdesc_cortex_a(char *buffer, size_t max_len)
{
size_t total = 0;
// We can't just repeatedly pass max_len to snprintf, because we keep changing the start
// of buffer (effectively changing its size), so we have to repeatedly compute the size
// passed to snprintf by subtracting the current total from max_len.
// ...Unless max_len is 0, in which case that subtraction will result in an (underflowed)
// negative number. So we also have to repeatedly check if max_len is 0 before performing
// that subtraction.
size_t printsz = max_len;
if (buffer != NULL)
memset(buffer, 0, max_len);
// Start with the "preamble", which is generic across ARM targets,
// ...save for one word, so we'll have to do the preamble in halves, and then we'll
// follow it with the GDB ARM Core feature tag.
total += snprintf(buffer, printsz,
"%s feature %s "
"<feature name=\"org.gnu.gdb.arm.core\">",
gdb_arm_preamble_first,
gdb_arm_preamble_second
);
// Then the general purpose registers, which have names of r0 to r12.
for (uint8_t i = 0; i <= 12; ++i) {
if (max_len != 0)
printsz = max_len - total;
total += snprintf(buffer + total, printsz, "<reg name=\"r%d\" bitsize=\"32\"/>", i);
}
// The special purpose registers are a slightly more complicated.
// Some of them have different types specified, however unlike the Cortex-M SPRs,
// all of the Cortex-A target description SPRs have the same bitsize, and none of them
// have a specified save-restore value. So we only need one "associative array" here.
for (size_t i = 0; i < ARRAY_SIZE(cortex_a_spr_names); ++i) {
gdb_reg_type_e type = cortex_a_spr_types[i];
if (max_len != 0)
printsz = max_len - total;
total += snprintf(buffer + total, printsz, "<reg name=\"%s\" bitsize=\"32\"%s/>",
cortex_a_spr_names[i],
gdb_reg_type_strings[type]
);
}
if (max_len != 0)
printsz = max_len - total;
// Now onto the floating point registers.
// The first register is unique; the rest all follow the same format.
total += snprintf(buffer + total, printsz,
"</feature>"
"<feature name=\"org.gnu.gdb.arm.vfp\">"
"<reg name=\"fpscr\" bitsize=\"32\"/>"
);
// Now onto the simple ones.
for (uint8_t i = 0; i <= 15; ++i) {
if (max_len != 0)
printsz = max_len - total;
total += snprintf(buffer + total, printsz,
"<reg name=\"d%d\" bitsize=\"64\" type=\"float\"/>",
i
);
}
if (max_len != 0)
printsz = max_len - total;
total += snprintf(buffer + total, printsz, "</feature></target>");
return total;
}
static void apb_write(target *t, uint16_t reg, uint32_t val)
{
@ -373,7 +498,13 @@ bool cortexa_probe(ADIv5_AP_t *apb, uint32_t debug_base)
t->attach = cortexa_attach;
t->detach = cortexa_detach;
t->tdesc = tdesc_cortex_a;
// Find the buffer size needed for the target description string we need to send to GDB,
// and then compute the string itself.
size_t size_needed = create_tdesc_cortex_a(NULL, 0) + 1;
char *buffer = malloc(size_needed);
create_tdesc_cortex_a(buffer, size_needed);
t->tdesc = buffer;
t->regs_read = cortexa_regs_read;
t->regs_write = cortexa_regs_write;
t->reg_read = cortexa_reg_read;

256
src/target/cortexm.c

@ -34,10 +34,16 @@
#include "target_internal.h"
#include "target_probe.h"
#include "cortexm.h"
#include "gdb_reg.h"
#include "command.h"
#include "gdb_packet.h"
#include "semihosting.h"
#include "platform.h"
#include <string.h>
#include <assert.h>
#include <malloc.h>
#include <stdio.h>
#include <unistd.h>
#if PC_HOSTED == 1
@ -134,8 +140,98 @@ static const uint32_t regnum_cortex_mf[] = {
0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, /* s24-s31 */
};
/* clang-format off */
static const char tdesc_cortex_m[] =
/**
* Fields for Cortex-M special purpose registers, used in the generation of GDB's target description XML.
* The general purpose registers r0-r12 and the vector floating point registers d0-d15 all follow a very
* regular format, so we only need to store fields for the special purpose registers.
* The arrays for each SPR field have the same order as each other, making each of them a pseudo
* 'associative array'.
*/
// Strings for the names of the Cortex-M's special purpose registers.
static const char *cortex_m_spr_names[] = {
"sp",
"lr",
"pc",
"xpsr",
"msp",
"psp",
"primask",
"basepri",
"faultmask",
"control",
};
// The "type" field for each Cortex-M special purpose register.
static const gdb_reg_type_e cortex_m_spr_types[] = {
GDB_TYPE_DATA_PTR, // sp
GDB_TYPE_CODE_PTR, // lr
GDB_TYPE_CODE_PTR, // pc
GDB_TYPE_UNSPECIFIED, // xpsr
GDB_TYPE_DATA_PTR, // msp
GDB_TYPE_DATA_PTR, // psp
GDB_TYPE_UNSPECIFIED, // primask
GDB_TYPE_UNSPECIFIED, // basepri
GDB_TYPE_UNSPECIFIED, // faultmask
GDB_TYPE_UNSPECIFIED, // control
};
static_assert(ARRAY_SIZE(cortex_m_spr_types) == ARRAY_SIZE(cortex_m_spr_names),
"SPR array length mismatch! SPR type array should have the same length as SPR name array."
);
// The "save-restore" field of each SPR.
static const gdb_reg_save_restore_e cortex_m_spr_save_restores[] = {
GDB_SAVE_RESTORE_UNSPECIFIED, // sp
GDB_SAVE_RESTORE_UNSPECIFIED, // lr
GDB_SAVE_RESTORE_UNSPECIFIED, // pc
GDB_SAVE_RESTORE_UNSPECIFIED, // xpsr
GDB_SAVE_RESTORE_NO, // msp
GDB_SAVE_RESTORE_NO, // psp
GDB_SAVE_RESTORE_NO, // primask
GDB_SAVE_RESTORE_NO, // basepri
GDB_SAVE_RESTORE_NO, // faultmask
GDB_SAVE_RESTORE_NO, // control
};
static_assert(ARRAY_SIZE(cortex_m_spr_save_restores) == ARRAY_SIZE(cortex_m_spr_names),
"SPR array length mismatch! SPR save-restore array should have the same length as SPR name array."
);
// The "bitsize" field of each SPR.
static const uint8_t cortex_m_spr_bitsizes[] = {
32, // sp
32, // lr
32, // pc
32, // xpsr
32, // msp
32, // psp
8, // primask
8, // basepri
8, // faultmask
8, // control
};
static_assert(ARRAY_SIZE(cortex_m_spr_bitsizes) == ARRAY_SIZE(cortex_m_spr_names),
"SPR array length mismatch! SPR bitsize array should have the same length as SPR name array."
);
// Creates the target description XML string for a Cortex-M. Like snprintf(), this function
// will write no more than max_len and returns the amount of bytes written. Or, if max_len is 0,
// then this function will return the amount of bytes that _would_ be necessary to create this
// string.
//
// This function is hand-optimized to decrease string duplication and thus code size, making it
// unforunately much less readable than the string literal it is equivalent to.
//
// The string it creates is XML-equivalent to the following:
/*
"<?xml version=\"1.0\"?>"
"<!DOCTYPE target SYSTEM \"gdb-target.dtd\">"
"<target>"
@ -165,9 +261,80 @@ static const char tdesc_cortex_m[] =
" <reg name=\"faultmask\" bitsize=\"8\" save-restore=\"no\"/>"
" <reg name=\"control\" bitsize=\"8\" save-restore=\"no\"/>"
" </feature>"
"</target>";
"</target>"
*/
static size_t create_tdesc_cortex_m(char *buffer, size_t max_len)
{
size_t total = 0;
// We can't just repeatedly pass max_len to snprintf, because we keep changing the start
// of buffer (effectively changing its size), so we have to repeatedly compute the size
// passed to snprintf by subtracting the current total from max_len.
// ...Unless max_len is 0, in which case that subtraction will result in an (underflowed)
// negative number. So we also have to repatedly check if max_len is 0 before performing
// that subtraction.
size_t printsz = max_len;
if (buffer != NULL)
memset(buffer, 0, max_len);
// Start with the "preamble", which is generic across ARM targets,
// ...save for one word, so we'll have to do the preamble in halves.
total += snprintf(buffer, printsz,
"%s target %s "
"<feature name=\"org.gnu.gdb.arm.m-profile\">",
gdb_arm_preamble_first,
gdb_arm_preamble_second
);
// Then the general purpose registers, which have names of r0 to r12,
// and all the same bitsize.
for (uint8_t i = 0; i <= 12; ++i) {
if (max_len != 0)
printsz = max_len - total;
total += snprintf(buffer + total, printsz, "<reg name=\"r%d\" bitsize=\"32\"/>", i);
}
// Now for sp, lr, pc, xpsr, msp, psp, primask, basepri, faultmask, and control.
// These special purpose registers are a little more complicated.
// Some of them have different bitsizes, specified types, or specified save-restore values.
// We'll use the 'associative arrays' defined for those values.
for (size_t i = 0; i < ARRAY_SIZE(cortex_m_spr_names); ++i) {
if (max_len != 0)
printsz = max_len - total;
gdb_reg_type_e type = cortex_m_spr_types[i];
gdb_reg_save_restore_e save_restore = cortex_m_spr_save_restores[i];
total += snprintf(buffer + total, printsz, "<reg name=\"%s\" bitsize=\"%d\"%s%s/>",
cortex_m_spr_names[i],
cortex_m_spr_bitsizes[i],
gdb_reg_save_restore_strings[save_restore],
gdb_reg_type_strings[type]
);
}
if (max_len != 0)
printsz = max_len - total;
total += snprintf(buffer + total, printsz, "</feature></target>");
return total;
}
static const char tdesc_cortex_mf[] =
// Creates the target description XML string for a Cortex-MF. Like snprintf(), this function
// will write no more than max_len and returns the amount of bytes written. Or, if max_len is 0,
// then this function will return the amount of bytes that _would_ be necessary to create this
// string.
//
// This function is hand-optimized to decrease string duplication and thus code size, making it
// unforunately much less readable than the string literal it is equivalent to.
//
// The string it creates is XML-equivalent to the following:
/*
"<?xml version=\"1.0\"?>"
"<!DOCTYPE target SYSTEM \"gdb-target.dtd\">"
"<target>"
@ -216,8 +383,52 @@ static const char tdesc_cortex_mf[] =
" <reg name=\"d14\" bitsize=\"64\" type=\"float\"/>"
" <reg name=\"d15\" bitsize=\"64\" type=\"float\"/>"
" </feature>"
"</target>";
/* clang-format on */
"</target>"
*/
static size_t create_tdesc_cortex_mf(char *buffer, size_t max_len)
{
// The first part of the target description for the Cortex-MF is identical to the Cortex-M
// target description.
size_t total = create_tdesc_cortex_m(buffer, max_len);
// We can't just repeatedly pass max_len to snprintf, because we keep changing the start
// of buffer (effectively changing its size), so we have to repeatedly compute the size
// passed to snprintf by subtracting the current total from max_len.
// ...Unless max_len is 0, in which case that subtraction will result in an (underflowed)
// negative number. So we also have to repatedly check if max_len is 0 before perofmring
// that subtraction.
size_t printsz = max_len;
if (max_len != 0) {
// Minor hack: subtract the target closing tag, since we have a bit more to add.
total -= strlen("</target>");
printsz = max_len - total;
}
total += snprintf(buffer + total, printsz,
"<feature name=\"org.gnu.gdb.arm.vfp\">"
"<reg name=\"fpscr\" bitsize=\"32\"/>"
);
// After fpscr, the rest of the vfp registers follow a regular format: d0-d15, bitsize 64, type float.
for (uint8_t i = 0; i <= 15; ++i) {
if (max_len != 0)
printsz = max_len - total;
total += snprintf(buffer + total, printsz, "<reg name=\"d%u\" bitsize=\"64\" type=\"float\"/>", i);
}
if (max_len != 0)
printsz = max_len - total;
total += snprintf(buffer + total, printsz, "</feature></target>");
return total;
}
ADIv5_AP_t *cortexm_ap(target *t)
{
@ -355,8 +566,32 @@ bool cortexm_probe(ADIv5_AP_t *ap)
t->attach = cortexm_attach;
t->detach = cortexm_detach;
/* Probe for FP extension. */
bool is_cortexmf = false;
uint32_t cpacr = target_mem_read32(t, CORTEXM_CPACR);
cpacr |= 0x00F00000; /* CP10 = 0b11, CP11 = 0b11 */
target_mem_write32(t, CORTEXM_CPACR, cpacr);
if (target_mem_read32(t, CORTEXM_CPACR) == cpacr)
is_cortexmf = true;
/* Should probe here to make sure it's Cortex-M3 */
t->tdesc = tdesc_cortex_m;
// Find the buffer size needed for the target description string we need to send to GDB,
// and then compute the string itself.
size_t size_needed;
char *buffer;
if (!is_cortexmf) {
size_needed = create_tdesc_cortex_m(NULL, 0) + 1;
buffer = malloc(size_needed);
create_tdesc_cortex_m(buffer, size_needed);
} else {
size_needed = create_tdesc_cortex_mf(NULL, 0) + 1;
buffer = malloc(size_needed);
create_tdesc_cortex_mf(buffer, size_needed);
}
t->tdesc = buffer;
t->regs_read = cortexm_regs_read;
t->regs_write = cortexm_regs_write;
t->reg_read = cortexm_reg_read;
@ -373,14 +608,9 @@ bool cortexm_probe(ADIv5_AP_t *ap)
target_add_commands(t, cortexm_cmd_list, cortexm_driver_str);
/* Probe for FP extension */
uint32_t cpacr = target_mem_read32(t, CORTEXM_CPACR);
cpacr |= 0x00F00000; /* CP10 = 0b11, CP11 = 0b11 */
target_mem_write32(t, CORTEXM_CPACR, cpacr);
if (target_mem_read32(t, CORTEXM_CPACR) == cpacr) {
if (is_cortexmf) {
t->target_options |= TOPT_FLAVOUR_V7MF;
t->regs_size += sizeof(regnum_cortex_mf);
t->tdesc = tdesc_cortex_mf;
}
/* Default vectors to catch */

46
src/target/gdb_reg.c

@ -0,0 +1,46 @@
/*
* This file is part of the Black Magic Debug project.
*
* Copyright (C) 2022 1bitsquared - Mikaela Szekely <mikaela.szekely@qyriad.me>
* Written by Mikaela Szekely <mikaela.szekely@qyriad.me>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "gdb_reg.h"
const char *gdb_arm_preamble_first =
"<?xml version=\"1.0\"?>"
"<!DOCTYPE";
const char *gdb_arm_preamble_second =
"SYSTEM "
"\"gdb-target.dtd\">"
"<target>"
" <architecture>arm</architecture>";
const char *gdb_reg_type_strings[] = {
"", // GDB_TYPE_UNSPECIFIED.
" type=\"data_ptr\"", // GDB_TYPE_DATA_PTR.
" type=\"code_ptr\"", // GDB_TYPE_CODE_PTR.
};
const char *gdb_reg_save_restore_strings[] = {
"", // GDB_SAVE_RESTORE_UNSPECIFIED.
" save-restore=\"no\"" // GDB_SAVE_RESTORE_NO.
};

58
src/target/gdb_reg.h

@ -0,0 +1,58 @@
/*
* This file is part of the Black Magic Debug project.
*
* Copyright (C) 2022 1bitsquared - Mikaela Szekely <mikaela.szekely@qyriad.me>
* Written by Mikaela Szekely <mikaela.szekely@qyriad.me>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef __GDB_REG_H
#define __GDB_REG_H
// The beginning XML for GDB target descriptions that are common to ARM targets,
// save for one word: the word after DOCTYPE, which is "target" for Cortex-M, and "feature"
// for Cortex-A. The "preamble" is thus split into two halves, with this single word missing
// and as the split point.
extern const char *gdb_arm_preamble_first;
// The beginning XML for GDB target descriptions that are common to ARM targets,
// save for one word: the word after DOCTYPE, which is "target" for Cortex-M, and "feature"
// for Cortex-A. The "preamble" is thus split into two halves, with this single word missing
// and as the split point.
extern const char *gdb_arm_preamble_second;
// The "type" field of a register tag.
typedef enum gdb_reg_type {
GDB_TYPE_UNSPECIFIED = 0,
GDB_TYPE_DATA_PTR,
GDB_TYPE_CODE_PTR,
} gdb_reg_type_e;
// The strings for the "type" field of a register tag, respective to its gdb_reg_type_e value.
extern const char *gdb_reg_type_strings[];
// The "save-restore" field of a register tag.
typedef enum gdb_reg_save_restore {
GDB_SAVE_RESTORE_UNSPECIFIED = 0,
GDB_SAVE_RESTORE_NO,
} gdb_reg_save_restore_e;
// The strings for the "save-restore" field of a register tag, respective to its gdb_reg_save_restore_e value.
extern const char *gdb_reg_save_restore_strings[];
#endif
Loading…
Cancel
Save