Re: [PATCH] gdb, gdbsupport, gdbserver: add support for printf 't' length modifier

Guinevere Larsen
2026-01-19T17:30:09Z

On 1/12/26 3:24 PM, simon.marchi@polymtl.ca wrote:
> From: Simon Marchi <simon.marchi@polymtl.ca>
>
> Tom de Vries reported [1] that a use of the `t` printf length modifier
> (used for ptrdiff_t) breaks "set debug dwarf-die 1":
>
> +break -qualified main
> Reading Reading compcomp unit at offset unit at offset 0x00x39
>
> Reading comp unit at offset 0x1a8
> Reading comp unit at offset 0x1e9
> Reading comp unit at offset 0x2c5
> Reading comp unit at offset 0x2a2
> Reading comp unit at offset 0x904
> Unrecognized format specifier 't' in printf
>
> This use is in dwarf2/read.c:
>
> gdb_printf (gdb_stdlog, "Read die from %s@0x%tx of %s:\n",
> m_die_section->get_name (),
> (begin_info_ptr - m_die_section->buffer),
> bfd_get_filename (m_abfd));
>
> Add support for the `t` length modifier in format_pieces, which
> gdb_printf ultimately uses (through ui_out::vmessage). Modify the three
> users of format_pieces: gdb's printf command, gdb's ui_out::vmessage
> function and gdbserver's ax_printf function.
>
> The implementation is mostly copied from what we do for size_t.
>
> Since format_pieces is also used to implement the printf command, this
> patch brings user-visible changes. Before:
>
> (gdb) printf "%td\n", -123
> ❌️ Unrecognized format specifier 't' in printf
>
> After:
>
> (gdb) printf "%td\n", -123
> -123
>
> [1] https://inbox.sourceware.org/gdb-patches/d4ccce34-aad5-4e3d-8fc9-efb2fc11275c@suse.de/
>
> Change-Id: Ie9fce78f5f48082d8f8a9ca2847b5ae26acaa60d
> ---

This commit seems to have caused a regression. After this, when starting
GDB without the quiet flag I get the following:

0x7ffc6b9bc520s
Copyright (C) 2026 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <0x7ffc6b9bc560s>
This is free software: you are free to change and redistribute it
There is NO WARRANTY, to the extent permitted by law.
Type "0x7ffc6b9bc5e0s" and "0x7ffc6b9bc5a0s" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "0x7ffc6b9bc620s" for configuration details.
For bug reporting instructions, please see:
0x7ffc6b9bc660s.

That is, anywhere where GDB would print a styled string, we seem to be
printing a pointer address instead.

> gdb/printcmd.c | 10 ++++++++++
> gdb/testsuite/gdb.base/printcmds.c | 6 ++++++
> gdb/testsuite/gdb.base/printcmds.exp | 4 ++++
> gdb/ui-out.c | 18 ++++++++++++++++++
> gdb/unittests/format_pieces-selftests.c | 12 ++++++++----
> gdbserver/ax.cc | 8 ++++++++
> gdbsupport/format.cc | 11 ++++++++++-
> gdbsupport/format.h | 2 +-
> 8 files changed, 65 insertions(+), 6 deletions(-)
>
> diff --git a/gdb/printcmd.c b/gdb/printcmd.c
> index 783b83615d02..7c97fc719926 100644
> --- a/gdb/printcmd.c
> +++ b/gdb/printcmd.c
> @@ -2815,6 +2815,16 @@ ui_printf (const char *arg, struct ui_file *stream)
> {
> size_t val = value_as_long (val_args[i]);
>
> + DIAGNOSTIC_PUSH
> + DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL
> + gdb_printf (stream, current_substring, val);
> + DIAGNOSTIC_POP
> + break;
> + }
> + case ptrdiff_t_arg:
> + {
> + ptrdiff_t val = value_as_long (val_args[i]);
> +
> DIAGNOSTIC_PUSH
> DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL
> gdb_printf (stream, current_substring, val);
> diff --git a/gdb/testsuite/gdb.base/printcmds.c b/gdb/testsuite/gdb.base/printcmds.c
> index 8445fcc1aa2a..82796932e1d3 100644
> --- a/gdb/testsuite/gdb.base/printcmds.c
> +++ b/gdb/testsuite/gdb.base/printcmds.c
> @@ -2,6 +2,8 @@
> It is explicitly unsigned to avoid differences due to native characters
> being either signed or unsigned. */
> #include <stdlib.h>
> +#include <stddef.h>
> +
> unsigned char ctable1[256] = {
> 0000, 0001, 0002, 0003, 0004, 0005, 0006, 0007,
> 0010, 0011, 0012, 0013, 0014, 0015, 0016, 0017,
> @@ -77,6 +79,10 @@ charptr teststring2 = "more contents";
>
> const char *teststring3 = "this is a longer test string that we can use";
>
> +/* For testing printf with %z (size_t) and %t (ptrdiff_t) length modifiers. */
> +size_t test_size = 1234;
> +ptrdiff_t test_ptrdiff = -5678;
> +
> /* Test printing of a struct containing character arrays. */
>
> struct some_arrays {
> diff --git a/gdb/testsuite/gdb.base/printcmds.exp b/gdb/testsuite/gdb.base/printcmds.exp
> index bc100f93df9a..32a952f01a55 100644
> --- a/gdb/testsuite/gdb.base/printcmds.exp
> +++ b/gdb/testsuite/gdb.base/printcmds.exp
> @@ -908,6 +908,10 @@ proc test_printf {} {
> # PR cli/14977.
> gdb_test "printf \"%s\\n\", 0" "\\(null\\)"
>
> + # Test %z (size_t) and %t (ptrdiff_t) length modifiers.
> + gdb_test "printf \"%zu\\n\", test_size" "1234"
> + gdb_test "printf \"%td\\n\", test_ptrdiff" "-5678"
> +
> with_max_value_size 20 {
> gdb_test {printf "%s", teststring3} \
> "^printed string requires 45 bytes, which is more than max-value-size"
> diff --git a/gdb/ui-out.c b/gdb/ui-out.c
> index 4f20e6b93477..87874e22530f 100644
> --- a/gdb/ui-out.c
> +++ b/gdb/ui-out.c
> @@ -682,6 +682,24 @@ ui_out::vmessage (const ui_file_style &in_style, const char *format,
> }
> }
> break;
> + case ptrdiff_t_arg:
> + {
> + ptrdiff_t val = va_arg (args, ptrdiff_t);
> + switch (piece.n_int_args)
> + {
> + case 0:
> + call_do_message (style, current_substring, val);
> + break;
> + case 1:
> + call_do_message (style, current_substring, intvals[0], val);
> + break;
> + case 2:
> + call_do_message (style, current_substring,
> + intvals[0], intvals[1], val);
> + break;
> + }
> + }
> + break;
> case double_arg:
> call_do_message (style, current_substring, va_arg (args, double));
> break;
> diff --git a/gdb/unittests/format_pieces-selftests.c b/gdb/unittests/format_pieces-selftests.c
> index af574ccc900f..c5f3cf74f1dc 100644
> --- a/gdb/unittests/format_pieces-selftests.c
> +++ b/gdb/unittests/format_pieces-selftests.c
> @@ -105,40 +105,44 @@ test_gdb_formats ()
> static void
> test_format_int_sizes ()
> {
> - check ("Hello\\t %hu%lu%llu%zu", /* ARI: %ll */
> + check ("Hello\\t %hu%lu%llu%zu%tu", /* ARI: %ll */
> {
> {"Hello\t ", literal_piece, 0},
> {"%hu", int_arg, 0},
> {"%lu", long_arg, 0},
> {"%" LL "u", long_long_arg, 0},
> {"%zu", size_t_arg, 0},
> + {"%tu", ptrdiff_t_arg, 0},
> });
>
> - check ("Hello\\t %hx%lx%llx%zx", /* ARI: %ll */
> + check ("Hello\\t %hx%lx%llx%zx%tx", /* ARI: %ll */
> {
> {"Hello\t ", literal_piece, 0},
> {"%hx", int_arg, 0},
> {"%lx", long_arg, 0},
> {"%" LL "x", long_long_arg, 0},
> {"%zx", size_t_arg, 0},
> + {"%tx", ptrdiff_t_arg, 0},
> });
>
> - check ("Hello\\t %ho%lo%llo%zo", /* ARI: %ll */
> + check ("Hello\\t %ho%lo%llo%zo%to", /* ARI: %ll */
> {
> {"Hello\t ", literal_piece, 0},
> {"%ho", int_arg, 0},
> {"%lo", long_arg, 0},
> {"%" LL "o", long_long_arg, 0},
> {"%zo", size_t_arg, 0},
> + {"%to", ptrdiff_t_arg, 0},
> });
>
> - check ("Hello\\t %hd%ld%lld%zd", /* ARI: %ll */
> + check ("Hello\\t %hd%ld%lld%zd%td", /* ARI: %ll */
> {
> {"Hello\t ", literal_piece, 0},
> {"%hd", int_arg, 0},
> {"%ld", long_arg, 0},
> {"%" LL "d", long_long_arg, 0},
> {"%zd", size_t_arg, 0},
> + {"%td", ptrdiff_t_arg, 0},
> });
> }
>
> diff --git a/gdbserver/ax.cc b/gdbserver/ax.cc
> index 44fcf3157291..4993bed2532c 100644
> --- a/gdbserver/ax.cc
> +++ b/gdbserver/ax.cc
> @@ -908,6 +908,14 @@ ax_printf (CORE_ADDR fn, CORE_ADDR chan, const char *format,
> break;
> }
>
> + case ptrdiff_t_arg:
> + {
> + ptrdiff_t val = args[i];
> +
> + printf (current_substring, val);
> + break;
> + }
> +
> case literal_piece:
> /* Print a portion of the format string that has no
> directives. Note that this will not include any
> diff --git a/gdbsupport/format.cc b/gdbsupport/format.cc
> index b515a95114e0..43995edf453e 100644
> --- a/gdbsupport/format.cc
> +++ b/gdbsupport/format.cc
> @@ -111,6 +111,7 @@ format_pieces::format_pieces (const char **arg, bool gdb_extensions,
> int seen_big_l = 0, seen_h = 0, seen_big_h = 0;
> int seen_big_d = 0, seen_double_big_d = 0;
> int seen_size_t = 0;
> + int seen_ptrdiff_t = 0;
> int bad = 0;
> int n_int_args = 0;
> bool seen_i64 = false;
> @@ -224,6 +225,11 @@ format_pieces::format_pieces (const char **arg, bool gdb_extensions,
> seen_size_t = 1;
> f++;
> break;
> + case 't':
> + /* For ptrdiff_t. */
> + seen_ptrdiff_t = 1;
> + f++;
> + break;
> case 'I':
> /* Support the Windows '%I64' extension, because an
> earlier call to format_pieces might have converted %lld
> @@ -257,6 +263,8 @@ format_pieces::format_pieces (const char **arg, bool gdb_extensions,
> case 'i':
> if (seen_size_t)
> this_argclass = size_t_arg;
> + else if (seen_ptrdiff_t)
> + this_argclass = ptrdiff_t_arg;
> else if (lcount == 0)
> this_argclass = int_arg;
> else if (lcount == 1)
> @@ -334,7 +342,8 @@ format_pieces::format_pieces (const char **arg, bool gdb_extensions,
>
> if (lcount > 1 || seen_h || seen_big_h || seen_big_h
> || seen_big_d || seen_double_big_d || seen_size_t
> - || seen_prec || seen_zero || seen_space || seen_plus)
> + || seen_ptrdiff_t || seen_prec || seen_zero || seen_space
> + || seen_plus)
> bad = 1;
>
> this_argclass = value_arg;
> diff --git a/gdbsupport/format.h b/gdbsupport/format.h
> index 91bcf1db55a6..ced10e46a716 100644
> --- a/gdbsupport/format.h
> +++ b/gdbsupport/format.h
> @@ -36,7 +36,7 @@
> enum argclass
> {
> literal_piece,
> - int_arg, long_arg, long_long_arg, size_t_arg, ptr_arg,
> + int_arg, long_arg, long_long_arg, size_t_arg, ptrdiff_t_arg, ptr_arg,
> string_arg, wide_string_arg, wide_char_arg,
> double_arg, long_double_arg,
> dec32float_arg, dec64float_arg, dec128float_arg,
>
> base-commit: b9517052ce5c1d87bfd7030bcbf90a98f63fddf6

--
Cheers,
Guinevere Larsen
It/she