Browse Source

target/cortex: dynamically generate tdesc strings to save code size

This commit removes the previous tdesc_cortex_a, tdesc_cortex_m, and
tdesc_cortex_mf XML string literals used for target description to GDB,
now instead programmatically generating them at runtime to significantly
deduplicate the characters that get embedded into the binary.

Output of ld's --print-memory-usage during final link before:
Memory region         Used Size  Region Size  %age Used
             rom:      116388 B       128 KB     88.80%
             ram:        3348 B        20 KB     16.35%

Output of ld's --print-memory-usage during final link now:
Memory region         Used Size  Region Size  %age Used
             rom:      113032 B       128 KB     86.24%
             ram:        3376 B        20 KB     16.48%

So all in all this saves 3356 bytes of flash.

Note: the exact size saved when compiled on your machine may differ, as
the size of the build seems at least partially non-deterministic.
We've gotten slightly different sizes (within 15 bytes of each other)
at different times, with the only differences being things like which
files were rebuilt in an incremental rebuild, or the order object files
were given to the linker command line. The numbers given above were the
numbers we got when testing the final builds from scratch, but all the
sizes we got were extremely similar to the sizes listed above.
pull/1196/head
Mikaela Szekely 2 years ago
parent
commit
9c1747f43d
  1. 1
      src/Makefile
  2. 139
      src/target/cortexa.c
  3. 258
      src/target/cortexm.c
  4. 46
      src/target/gdb_reg.c
  5. 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 \

139
src/target/cortexa.c

@ -31,8 +31,13 @@
#include "exception.h"
#include "adiv5.h"
#include "target.h"
#include "gdb_reg.h"
#include "target_internal.h"
#include <assert.h>
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
static const char cortexa_driver_str[] = "ARM Cortex-A";
static bool cortexa_attach(target *t);
@ -154,8 +159,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 +242,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 +500,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;

258
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
@ -57,6 +63,8 @@
#include <fcntl.h>
#endif
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
static const char cortexm_driver_str[] = "ARM Cortex-M";
static bool cortexm_vector_catch(target *t, int argc, char *argv[]);
@ -134,8 +142,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 +263,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 +385,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 +568,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 +610,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