From 9c1747f43de7c502ab4be84e3fd1a9c35d370ec6 Mon Sep 17 00:00:00 2001 From: Mikaela Szekely Date: Fri, 5 Aug 2022 16:43:09 -0600 Subject: [PATCH] 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. --- src/Makefile | 1 + src/target/cortexa.c | 139 ++++++++++++++++++++++- src/target/cortexm.c | 258 ++++++++++++++++++++++++++++++++++++++++--- src/target/gdb_reg.c | 46 ++++++++ src/target/gdb_reg.h | 58 ++++++++++ 5 files changed, 486 insertions(+), 16 deletions(-) create mode 100644 src/target/gdb_reg.c create mode 100644 src/target/gdb_reg.h diff --git a/src/Makefile b/src/Makefile index 03f0db19..a3f818ab 100644 --- a/src/Makefile +++ b/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 \ diff --git a/src/target/cortexa.c b/src/target/cortexa.c index 3c765d8e..ca6c386c 100644 --- a/src/target/cortexa.c +++ b/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 + +#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: +/* "" "" "" @@ -199,6 +242,90 @@ static const char tdesc_cortex_a[] = " " " " ""; +*/ +// 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 " + "", + 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, "", 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, "", + 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, + "" + "" + "" + ); + + // 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, + "", + i + ); + } + + if (max_len != 0) + printsz = max_len - total; + + total += snprintf(buffer + total, printsz, ""); + + 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; diff --git a/src/target/cortexm.c b/src/target/cortexm.c index 0c06cad3..a6ca3ae8 100644 --- a/src/target/cortexm.c +++ b/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 +#include +#include +#include #include #if PC_HOSTED == 1 @@ -57,6 +63,8 @@ #include #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: +/* "" "" "" @@ -165,9 +263,80 @@ static const char tdesc_cortex_m[] = " " " " " " - ""; + "" +*/ +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 " + "", + 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, "", 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, "", + 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, ""); + + 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: +/* "" "" "" @@ -216,8 +385,52 @@ static const char tdesc_cortex_mf[] = " " " " " " - ""; -/* clang-format on */ + "" +*/ +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(""); + + printsz = max_len - total; + } + + + total += snprintf(buffer + total, printsz, + "" + "" + ); + + // 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, "", i); + } + + if (max_len != 0) + printsz = max_len - total; + + total += snprintf(buffer + total, printsz, ""); + + 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 */ diff --git a/src/target/gdb_reg.c b/src/target/gdb_reg.c new file mode 100644 index 00000000..efc59d0a --- /dev/null +++ b/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 + * Written by Mikaela Szekely + * + * 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 . + */ + +#include "gdb_reg.h" + + +const char *gdb_arm_preamble_first = + "" + "" + "" + " arm"; + + + +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. +}; diff --git a/src/target/gdb_reg.h b/src/target/gdb_reg.h new file mode 100644 index 00000000..ddd7a408 --- /dev/null +++ b/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 + * Written by Mikaela Szekely + * + * 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 . + */ + +#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