diff mbox series

[RFC] gdbstub: Re-factor gdb command extensions

Message ID 20240716114229.329355-1-alex.bennee@linaro.org (mailing list archive)
State New, archived
Headers show
Series [RFC] gdbstub: Re-factor gdb command extensions | expand

Commit Message

Alex Bennée July 16, 2024, 11:42 a.m. UTC
Coverity reported a memory leak (CID 1549757) in this code and its
admittedly rather clumsy handling of extending the command table.
Instead of handing over a full array of the commands lets use the
lighter weight GPtrArray and simply test for the presence of each
entry as we go. This avoids complications of transferring ownership of
arrays and keeps the final command entries as static entries in the
target code.

Cc: Akihiko Odaki <akihiko.odaki@daynix.com>
Cc: Gustavo Bueno Romero <gustavo.romero@linaro.org>
Cc: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
---
 include/gdbstub/commands.h |  19 ++++--
 target/arm/internals.h     |   4 +-
 gdbstub/gdbstub.c          | 127 +++++++++++++++++++++----------------
 target/arm/gdbstub.c       |  16 ++---
 target/arm/gdbstub64.c     |  11 ++--
 5 files changed, 95 insertions(+), 82 deletions(-)

Comments

Gustavo Romero July 16, 2024, 12:42 p.m. UTC | #1
Hi Alex,

On 7/16/24 8:42 AM, Alex Bennée wrote:
> Coverity reported a memory leak (CID 1549757) in this code and its
> admittedly rather clumsy handling of extending the command table.
> Instead of handing over a full array of the commands lets use the
> lighter weight GPtrArray and simply test for the presence of each
> entry as we go. This avoids complications of transferring ownership of
> arrays and keeps the final command entries as static entries in the
> target code.
How did you run Coverity to find the leak? I'm wondering what's the
quickest way to check it next time.


> Cc: Akihiko Odaki <akihiko.odaki@daynix.com>
> Cc: Gustavo Bueno Romero <gustavo.romero@linaro.org>
> Cc: Peter Maydell <peter.maydell@linaro.org>
> Signed-off-by: Alex Bennée <alex.bennee@linaro.org>

Reviewed-by: Gustavo Romero <gustavo.romero@linaro.org>


Cheers,
Gustavo
Alex Bennée July 16, 2024, 1:48 p.m. UTC | #2
Gustavo Romero <gustavo.romero@linaro.org> writes:

> Hi Alex,
>
> On 7/16/24 8:42 AM, Alex Bennée wrote:
>> Coverity reported a memory leak (CID 1549757) in this code and its
>> admittedly rather clumsy handling of extending the command table.
>> Instead of handing over a full array of the commands lets use the
>> lighter weight GPtrArray and simply test for the presence of each
>> entry as we go. This avoids complications of transferring ownership of
>> arrays and keeps the final command entries as static entries in the
>> target code.
> How did you run Coverity to find the leak? I'm wondering what's the
> quickest way to check it next time.

Coverity is only run in the cloud on the released build. There is a
container somewhere but I don't know how its used.

I did test on a build with --enable-sanitizers though.

>
>
>> Cc: Akihiko Odaki <akihiko.odaki@daynix.com>
>> Cc: Gustavo Bueno Romero <gustavo.romero@linaro.org>
>> Cc: Peter Maydell <peter.maydell@linaro.org>
>> Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
>
> Reviewed-by: Gustavo Romero <gustavo.romero@linaro.org>
>
>
> Cheers,
> Gustavo
Peter Maydell July 16, 2024, 2:09 p.m. UTC | #3
On Tue, 16 Jul 2024 at 14:48, Alex Bennée <alex.bennee@linaro.org> wrote:
>
> Gustavo Romero <gustavo.romero@linaro.org> writes:
>
> > Hi Alex,
> >
> > On 7/16/24 8:42 AM, Alex Bennée wrote:
> >> Coverity reported a memory leak (CID 1549757) in this code and its
> >> admittedly rather clumsy handling of extending the command table.
> >> Instead of handing over a full array of the commands lets use the
> >> lighter weight GPtrArray and simply test for the presence of each
> >> entry as we go. This avoids complications of transferring ownership of
> >> arrays and keeps the final command entries as static entries in the
> >> target code.
> > How did you run Coverity to find the leak? I'm wondering what's the
> > quickest way to check it next time.
>
> Coverity is only run in the cloud on the released build. There is a
> container somewhere but I don't know how its used.

The Coverity cloud stuff comes in two parts:
 (1) you build locally with the Coverity tools, which creates
a big opaque build-artefact
 (2) you upload that artefact to the cloud server, and the
actual analysis happens on the cloud

The container stuff and the integration with the Gitlab CI
is there for the sole purpose of automating the "local build
and upload" steps. You can't do an analysis-run locally.
(Well, you probably can if you have the paid-for commercial
version of the tooling, but we haven't got any kind of
setup for doing that.)

We only do analysis runs on head-of-git because the Coverity Scan
resource limits for open source projects give us about one
complete scan a day. So this is all "after the fact" stuff.
Developers who want to look at the scan results can create
an account via https://scan.coverity.com/projects/qemu .
Triaging new coverity reports is a bit tedious because there
are a ton of false positives...

thanks
-- PMM
Gustavo Romero July 16, 2024, 3:13 p.m. UTC | #4
Hi Peter,

On 7/16/24 11:09 AM, Peter Maydell wrote:
> On Tue, 16 Jul 2024 at 14:48, Alex Bennée <alex.bennee@linaro.org> wrote:
>>
>> Gustavo Romero <gustavo.romero@linaro.org> writes:
>>
>>> Hi Alex,
>>>
>>> On 7/16/24 8:42 AM, Alex Bennée wrote:
>>>> Coverity reported a memory leak (CID 1549757) in this code and its
>>>> admittedly rather clumsy handling of extending the command table.
>>>> Instead of handing over a full array of the commands lets use the
>>>> lighter weight GPtrArray and simply test for the presence of each
>>>> entry as we go. This avoids complications of transferring ownership of
>>>> arrays and keeps the final command entries as static entries in the
>>>> target code.
>>> How did you run Coverity to find the leak? I'm wondering what's the
>>> quickest way to check it next time.
>>
>> Coverity is only run in the cloud on the released build. There is a
>> container somewhere but I don't know how its used.
> 
> The Coverity cloud stuff comes in two parts:
>   (1) you build locally with the Coverity tools, which creates
> a big opaque build-artefact
>   (2) you upload that artefact to the cloud server, and the
> actual analysis happens on the cloud
> 
> The container stuff and the integration with the Gitlab CI
> is there for the sole purpose of automating the "local build
> and upload" steps. You can't do an analysis-run locally.
> (Well, you probably can if you have the paid-for commercial
> version of the tooling, but we haven't got any kind of
> setup for doing that.)
> 
> We only do analysis runs on head-of-git because the Coverity Scan
> resource limits for open source projects give us about one
> complete scan a day. So this is all "after the fact" stuff.
> Developers who want to look at the scan results can create
> an account via https://scan.coverity.com/projects/qemu .
> Triaging new coverity reports is a bit tedious because there
> are a ton of false positives...

Thanks for the explanation!


Cheers,
Gustavo
Richard Henderson July 16, 2024, 4:33 p.m. UTC | #5
On 7/16/24 21:42, Alex Bennée wrote:
>   void gdb_extend_qsupported_features(char *qsupported_features)
>   {
> -    /*
> -     * We don't support different sets of CPU gdb features on different CPUs yet
> -     * so assert the feature strings are the same on all CPUs, or is set only
> -     * once (1 CPU).
> -     */
> -    g_assert(extended_qsupported_features == NULL ||
> -             g_strcmp0(extended_qsupported_features, qsupported_features) == 0);
> -
> -    extended_qsupported_features = qsupported_features;
> +    if (!extended_qsupported_features) {
> +        extended_qsupported_features = g_strdup(qsupported_features);
> +    } else if (!g_strrstr(extended_qsupported_features, qsupported_features)) {

Did you really need the last instance of the substring?

I'll note that g_strrstr is quite simplistic, whereas strstr has a much more scalable 
algorithm.


> +        char *old = extended_qsupported_features;
> +        extended_qsupported_features = g_strdup_printf("%s%s", old, qsupported_features);

Right tool for the right job, please: g_strconcat().

That said, did you *really* want to concatenate now, and have to search through the 
middle, as opposed to storing N strings separately?  You could defer the concat until the 
actual negotiation with gdb.  That would reduce strstr above to a loop over strcmp.

> +    for (int i = 0; i < extensions->len; i++) {
> +        gpointer entry = g_ptr_array_index(extensions, i);
> +        if (!g_ptr_array_find(table, entry, NULL)) {
> +            g_ptr_array_add(table, entry);

Are you expecting the same GdbCmdParseEntry object to be registered multiple times?  Can 
we fix that at a higher level?


r~
Alex Bennée July 16, 2024, 4:55 p.m. UTC | #6
Richard Henderson <richard.henderson@linaro.org> writes:

> On 7/16/24 21:42, Alex Bennée wrote:
>>   void gdb_extend_qsupported_features(char *qsupported_features)
>>   {
>> -    /*
>> -     * We don't support different sets of CPU gdb features on different CPUs yet
>> -     * so assert the feature strings are the same on all CPUs, or is set only
>> -     * once (1 CPU).
>> -     */
>> -    g_assert(extended_qsupported_features == NULL ||
>> -             g_strcmp0(extended_qsupported_features, qsupported_features) == 0);
>> -
>> -    extended_qsupported_features = qsupported_features;
>> +    if (!extended_qsupported_features) {
>> +        extended_qsupported_features = g_strdup(qsupported_features);
>> +    } else if (!g_strrstr(extended_qsupported_features, qsupported_features)) {
>
> Did you really need the last instance of the substring?

Not really - I just want to check the string hasn't been added before.

>
> I'll note that g_strrstr is quite simplistic, whereas strstr has a
> much more scalable algorithm.
>
>
>> +        char *old = extended_qsupported_features;
>> +        extended_qsupported_features = g_strdup_printf("%s%s", old, qsupported_features);
>
> Right tool for the right job, please: g_strconcat().
>
> That said, did you *really* want to concatenate now, and have to
> search through the middle, as opposed to storing N strings separately?
> You could defer the concat until the actual negotiation with gdb.
> That would reduce strstr above to a loop over strcmp.
>
>> +    for (int i = 0; i < extensions->len; i++) {
>> +        gpointer entry = g_ptr_array_index(extensions, i);
>> +        if (!g_ptr_array_find(table, entry, NULL)) {
>> +            g_ptr_array_add(table, entry);
>
> Are you expecting the same GdbCmdParseEntry object to be registered
> multiple times?  Can we fix that at a higher level?

Its basically a hack to deal with the fact everything is tied to the
CPUObject so we register everything multiple times. We could do a if
(!registerd) register() dance but I guess I'm thinking forward to a
hydrogenous future but I guess we'd need to do more work then anyway.

>
>
> r~
Richard Henderson July 16, 2024, 9:53 p.m. UTC | #7
On 7/17/24 02:55, Alex Bennée wrote:
>> Are you expecting the same GdbCmdParseEntry object to be registered
>> multiple times?  Can we fix that at a higher level?
> 
> Its basically a hack to deal with the fact everything is tied to the
> CPUObject so we register everything multiple times. We could do a if
> (!registerd) register() dance but I guess I'm thinking forward to a
> hydrogenous future but I guess we'd need to do more work then anyway.

Any chance we could move it all to the CPUClass?


r~
Alex Bennée July 17, 2024, 8:24 a.m. UTC | #8
Richard Henderson <richard.henderson@linaro.org> writes:

> On 7/17/24 02:55, Alex Bennée wrote:
>>> Are you expecting the same GdbCmdParseEntry object to be registered
>>> multiple times?  Can we fix that at a higher level?
>> Its basically a hack to deal with the fact everything is tied to the
>> CPUObject so we register everything multiple times. We could do a if
>> (!registerd) register() dance but I guess I'm thinking forward to a
>> hydrogenous future but I guess we'd need to do more work then anyway.
>
> Any chance we could move it all to the CPUClass?

We would have to move quite a bunch of stuff, including the system
register creation code. I don't think that's a re-factor I want to
attempt quite so close to soft-freeze.

>
>
> r~
Peter Maydell July 17, 2024, 12:59 p.m. UTC | #9
On Tue, 16 Jul 2024 at 22:53, Richard Henderson
<richard.henderson@linaro.org> wrote:
>
> On 7/17/24 02:55, Alex Bennée wrote:
> >> Are you expecting the same GdbCmdParseEntry object to be registered
> >> multiple times?  Can we fix that at a higher level?
> >
> > Its basically a hack to deal with the fact everything is tied to the
> > CPUObject so we register everything multiple times. We could do a if
> > (!registerd) register() dance but I guess I'm thinking forward to a
> > hydrogenous future but I guess we'd need to do more work then anyway.
>
> Any chance we could move it all to the CPUClass?

No, because different instances of the same CPUClass might
have different feature sets. In this case, one CPU might have
MTE and another not, or one be AArch64 and another not.

The underlying problem here is that there's quite a lot here
that potentially varies across different CPUs in the system,
but the gdbstub layer has an assumption of heterogeneity.
(cf also the stuff about system registers.)

thanks
-- PMM
diff mbox series

Patch

diff --git a/include/gdbstub/commands.h b/include/gdbstub/commands.h
index f3058f9dda..40f0514fe9 100644
--- a/include/gdbstub/commands.h
+++ b/include/gdbstub/commands.h
@@ -74,23 +74,28 @@  int gdb_put_packet(const char *buf);
 
 /**
  * gdb_extend_query_table() - Extend query table.
- * @table: The table with the additional query packet handlers.
- * @size: The number of handlers to be added.
+ * @table: GPtrArray of GdbCmdParseEntry entries.
+ *
+ * The caller should free @table afterwards
  */
-void gdb_extend_query_table(GdbCmdParseEntry *table, int size);
+void gdb_extend_query_table(GPtrArray *table);
 
 /**
  * gdb_extend_set_table() - Extend set table.
- * @table: The table with the additional set packet handlers.
- * @size: The number of handlers to be added.
+ * @table: GPtrArray of GdbCmdParseEntry entries.
+ *
+ * The caller should free @table afterwards
  */
-void gdb_extend_set_table(GdbCmdParseEntry *table, int size);
+void gdb_extend_set_table(GPtrArray *table);
 
 /**
  * gdb_extend_qsupported_features() - Extend the qSupported features string.
  * @qsupported_features: The additional qSupported feature(s) string. The string
  * should start with a semicolon and, if there are more than one feature, the
- * features should be separate by a semiocolon.
+ * features should be separate by a semicolon.
+ *
+ * The caller should free @qsupported_features afterwards if
+ * dynamically allocated.
  */
 void gdb_extend_qsupported_features(char *qsupported_features);
 
diff --git a/target/arm/internals.h b/target/arm/internals.h
index da22d04121..757b1fae92 100644
--- a/target/arm/internals.h
+++ b/target/arm/internals.h
@@ -359,8 +359,8 @@  void arm_cpu_register_gdb_regs_for_features(ARMCPU *cpu);
 void arm_translate_init(void);
 
 void arm_cpu_register_gdb_commands(ARMCPU *cpu);
-void aarch64_cpu_register_gdb_commands(ARMCPU *cpu, GString *, GArray *,
-                                       GArray *);
+void aarch64_cpu_register_gdb_commands(ARMCPU *cpu, GString *,
+                                       GPtrArray *, GPtrArray *);
 
 void arm_restore_state_to_opc(CPUState *cs,
                               const TranslationBlock *tb,
diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c
index b9ad0a063e..fd7b4ecddb 100644
--- a/gdbstub/gdbstub.c
+++ b/gdbstub/gdbstub.c
@@ -1615,17 +1615,16 @@  static void handle_query_thread_extra(GArray *params, void *user_ctx)
 }
 
 static char *extended_qsupported_features;
+
 void gdb_extend_qsupported_features(char *qsupported_features)
 {
-    /*
-     * We don't support different sets of CPU gdb features on different CPUs yet
-     * so assert the feature strings are the same on all CPUs, or is set only
-     * once (1 CPU).
-     */
-    g_assert(extended_qsupported_features == NULL ||
-             g_strcmp0(extended_qsupported_features, qsupported_features) == 0);
-
-    extended_qsupported_features = qsupported_features;
+    if (!extended_qsupported_features) {
+        extended_qsupported_features = g_strdup(qsupported_features);
+    } else if (!g_strrstr(extended_qsupported_features, qsupported_features)) {
+        char *old = extended_qsupported_features;
+        extended_qsupported_features = g_strdup_printf("%s%s", old, qsupported_features);
+        g_free(old);
+    }
 }
 
 static void handle_query_supported(GArray *params, void *user_ctx)
@@ -1753,39 +1752,59 @@  static const GdbCmdParseEntry gdb_gen_query_set_common_table[] = {
     },
 };
 
-/* Compares if a set of command parsers is equal to another set of parsers. */
-static bool cmp_cmds(GdbCmdParseEntry *c, GdbCmdParseEntry *d, int size)
+/**
+ * extend_table() - extend one of the command tables
+ * @table: the command table to extend (or NULL)
+ * @extensions: a list of GdbCmdParseEntry pointers
+ *
+ * The entries themselves should be pointers to static const
+ * GdbCmdParseEntry entries. If the entry is already in the table we
+ * skip adding it again.
+ *
+ * Returns (a potentially freshly allocated) GPtrArray of GdbCmdParseEntry
+ */
+static GPtrArray * extend_table(GPtrArray *table, GPtrArray *extensions)
 {
-    for (int i = 0; i < size; i++) {
-        if (!(c[i].handler == d[i].handler &&
-            g_strcmp0(c[i].cmd, d[i].cmd) == 0 &&
-            c[i].cmd_startswith == d[i].cmd_startswith &&
-            g_strcmp0(c[i].schema, d[i].schema) == 0)) {
+    if (!table) {
+        table = g_ptr_array_new();
+    }
 
-            /* Sets are different. */
-            return false;
+
+    for (int i = 0; i < extensions->len; i++) {
+        gpointer entry = g_ptr_array_index(extensions, i);
+        if (!g_ptr_array_find(table, entry, NULL)) {
+            g_ptr_array_add(table, entry);
         }
     }
 
-    /* Sets are equal, i.e. contain the same command parsers. */
-    return true;
+    return table;
 }
 
-static GdbCmdParseEntry *extended_query_table;
-static int extended_query_table_size;
-void gdb_extend_query_table(GdbCmdParseEntry *table, int size)
+/**
+ * process_extended_table() - run through an extended command table
+ * @table: the command table to check
+ * @data: parameters
+ *
+ * returns true if the command was found and executed
+ */
+static bool process_extended_table(GPtrArray *table, const char *data)
 {
-    /*
-     * We don't support different sets of CPU gdb features on different CPUs yet
-     * so assert query table is the same on all CPUs, or is set only once
-     * (1 CPU).
-     */
-    g_assert(extended_query_table == NULL ||
-             (extended_query_table_size == size &&
-              cmp_cmds(extended_query_table, table, size)));
+    for (int i = 0; i < table->len; i++) {
+        const GdbCmdParseEntry *entry = g_ptr_array_index(table, i);
+        if (process_string_cmd(data, entry, 1)) {
+            return true;
+        }
+    }
+    return false;
+}
 
-    extended_query_table = table;
-    extended_query_table_size = size;
+
+/* Ptr to GdbCmdParseEntry */
+static GPtrArray *extended_query_table;
+
+void gdb_extend_query_table(GPtrArray *new_queries)
+{
+    extended_query_table = extend_table(extended_query_table, new_queries);
 }
 
 static const GdbCmdParseEntry gdb_gen_query_table[] = {
@@ -1880,20 +1899,12 @@  static const GdbCmdParseEntry gdb_gen_query_table[] = {
 #endif
 };
 
-static GdbCmdParseEntry *extended_set_table;
-static int extended_set_table_size;
-void gdb_extend_set_table(GdbCmdParseEntry *table, int size)
-{
-    /*
-     * We don't support different sets of CPU gdb features on different CPUs yet
-     * so assert set table is the same on all CPUs, or is set only once (1 CPU).
-     */
-    g_assert(extended_set_table == NULL ||
-             (extended_set_table_size == size &&
-              cmp_cmds(extended_set_table, table, size)));
+/* Ptr to GdbCmdParseEntry */
+static GPtrArray *extended_set_table;
 
-    extended_set_table = table;
-    extended_set_table_size = size;
+void gdb_extend_set_table(GPtrArray *new_set)
+{
+    extended_set_table = extend_table(extended_set_table, new_set);
 }
 
 static const GdbCmdParseEntry gdb_gen_set_table[] = {
@@ -1924,26 +1935,28 @@  static const GdbCmdParseEntry gdb_gen_set_table[] = {
 
 static void handle_gen_query(GArray *params, void *user_ctx)
 {
+    const char *data;
+
     if (!params->len) {
         return;
     }
 
-    if (process_string_cmd(gdb_get_cmd_param(params, 0)->data,
+    data = gdb_get_cmd_param(params, 0)->data;
+
+    if (process_string_cmd(data,
                            gdb_gen_query_set_common_table,
                            ARRAY_SIZE(gdb_gen_query_set_common_table))) {
         return;
     }
 
-    if (process_string_cmd(gdb_get_cmd_param(params, 0)->data,
+    if (process_string_cmd(data,
                            gdb_gen_query_table,
                            ARRAY_SIZE(gdb_gen_query_table))) {
         return;
     }
 
     if (extended_query_table &&
-        process_string_cmd(gdb_get_cmd_param(params, 0)->data,
-                           extended_query_table,
-                           extended_query_table_size)) {
+        process_extended_table(extended_query_table, data)) {
         return;
     }
 
@@ -1953,26 +1966,28 @@  static void handle_gen_query(GArray *params, void *user_ctx)
 
 static void handle_gen_set(GArray *params, void *user_ctx)
 {
+    const char *data;
+
     if (!params->len) {
         return;
     }
 
-    if (process_string_cmd(gdb_get_cmd_param(params, 0)->data,
+    data = gdb_get_cmd_param(params, 0)->data;
+
+    if (process_string_cmd(data,
                            gdb_gen_query_set_common_table,
                            ARRAY_SIZE(gdb_gen_query_set_common_table))) {
         return;
     }
 
-    if (process_string_cmd(gdb_get_cmd_param(params, 0)->data,
+    if (process_string_cmd(data,
                            gdb_gen_set_table,
                            ARRAY_SIZE(gdb_gen_set_table))) {
         return;
     }
 
     if (extended_set_table &&
-        process_string_cmd(gdb_get_cmd_param(params, 0)->data,
-                           extended_set_table,
-                           extended_set_table_size)) {
+        process_extended_table(extended_set_table, data)) {
         return;
     }
 
diff --git a/target/arm/gdbstub.c b/target/arm/gdbstub.c
index c3a9b5eb1e..554b8736bb 100644
--- a/target/arm/gdbstub.c
+++ b/target/arm/gdbstub.c
@@ -477,11 +477,9 @@  static GDBFeature *arm_gen_dynamic_m_secextreg_feature(CPUState *cs,
 
 void arm_cpu_register_gdb_commands(ARMCPU *cpu)
 {
-    GArray *query_table =
-        g_array_new(FALSE, FALSE, sizeof(GdbCmdParseEntry));
-    GArray *set_table =
-        g_array_new(FALSE, FALSE, sizeof(GdbCmdParseEntry));
-    GString *qsupported_features = g_string_new(NULL);
+    g_autoptr(GPtrArray) query_table = g_ptr_array_new();
+    g_autoptr(GPtrArray) set_table = g_ptr_array_new();
+    g_autoptr(GString) qsupported_features = g_string_new(NULL);
 
     if (arm_feature(&cpu->env, ARM_FEATURE_AARCH64)) {
     #ifdef TARGET_AARCH64
@@ -492,16 +490,12 @@  void arm_cpu_register_gdb_commands(ARMCPU *cpu)
 
     /* Set arch-specific handlers for 'q' commands. */
     if (query_table->len) {
-        gdb_extend_query_table(&g_array_index(query_table,
-                                              GdbCmdParseEntry, 0),
-                                              query_table->len);
+        gdb_extend_query_table(query_table);
     }
 
     /* Set arch-specific handlers for 'Q' commands. */
     if (set_table->len) {
-        gdb_extend_set_table(&g_array_index(set_table,
-                             GdbCmdParseEntry, 0),
-                             set_table->len);
+        gdb_extend_set_table(set_table);
     }
 
     /* Set arch-specific qSupported feature. */
diff --git a/target/arm/gdbstub64.c b/target/arm/gdbstub64.c
index 2e2bc2700b..c8cef8cbc0 100644
--- a/target/arm/gdbstub64.c
+++ b/target/arm/gdbstub64.c
@@ -564,7 +564,7 @@  enum Command {
     NUM_CMDS
 };
 
-static GdbCmdParseEntry cmd_handler_table[NUM_CMDS] = {
+static const GdbCmdParseEntry cmd_handler_table[NUM_CMDS] = {
     [qMemTags] = {
         .handler = handle_q_memtag,
         .cmd_startswith = true,
@@ -590,17 +590,16 @@  static GdbCmdParseEntry cmd_handler_table[NUM_CMDS] = {
 #endif /* CONFIG_USER_ONLY */
 
 void aarch64_cpu_register_gdb_commands(ARMCPU *cpu, GString *qsupported,
-                                       GArray *qtable, GArray *stable)
+                                       GPtrArray *qtable, GPtrArray *stable)
 {
 #ifdef CONFIG_USER_ONLY
     /* MTE */
     if (cpu_isar_feature(aa64_mte, cpu)) {
         g_string_append(qsupported, ";memory-tagging+");
 
-        g_array_append_val(qtable, cmd_handler_table[qMemTags]);
-        g_array_append_val(qtable, cmd_handler_table[qIsAddressTagged]);
-
-        g_array_append_val(stable, cmd_handler_table[QMemTags]);
+        g_ptr_array_add(qtable, (gpointer) &cmd_handler_table[qMemTags]);
+        g_ptr_array_add(qtable, (gpointer) &cmd_handler_table[qIsAddressTagged]);
+        g_ptr_array_add(stable, (gpointer) &cmd_handler_table[QMemTags]);
     }
 #endif
 }