From 572b0825c73613a562e6a2738105f8d9b2577996 Mon Sep 17 00:00:00 2001 From: Nick Alcock Date: Mon, 22 Sep 2025 21:07:11 +0100 Subject: [PATCH 1/2] libctf: fix querying of large structures After GCC PR 121411 is fixed, large structures (with offsets > 512MiB) are written correctly... but libctf cannot query them properly unless they are even bigger (> 4GiB), because it checks to see if the ctt_size is CTF_LSIZE_SENT to decide whether to use a ctf_lmember_t or a ctf_member_t to encode the structure members. But the structure member offsets are in *bits*, not bytes: the right value to check is CTF_LSTRUCT_THRESH, which is 1/8th the size. (Thanks to Martin Pirker for the diagnosis and fix.) Testing this is a bit fun, because we don't want to emit an error if the compiler is broken: but we cannot tell whether the compiler is broken using the existing lookup harness, because its input is passed through the linker (and thus the broken ld). So add another sort of link mode, "objects", which keeps the constituent object files around and passes both the final linker output and the object files that make it up to the lookup program. Our testcase can then check the linker input to see if the compiler is buggy, and only if it isn't check the linker output and fail if things aren't right. libctf/ PR libctf/33339 * ctf-types.c (ctf_struct_member): Check CTF_LSTRUCT_THRESH, not CTF_LSIZE_SENT. * testsuite/lib/ctf-lib.exp (run_lookup_test): New 'objects' link option. * testsuite/libctf-lookup/big-struct-corruption.*: New test. * testsuite/libctf-lookup/big-struct-ctf.c: New test input. --- libctf/ctf-types.c | 2 +- libctf/testsuite/lib/ctf-lib.exp | 43 +++++-- .../libctf-lookup/big-struct-corruption.c | 118 ++++++++++++++++++ .../libctf-lookup/big-struct-corruption.lk | 3 + .../testsuite/libctf-lookup/big-struct-ctf.c | 72 +++++++++++ 5 files changed, 228 insertions(+), 10 deletions(-) create mode 100644 libctf/testsuite/libctf-lookup/big-struct-corruption.c create mode 100644 libctf/testsuite/libctf-lookup/big-struct-corruption.lk create mode 100644 libctf/testsuite/libctf-lookup/big-struct-ctf.c diff --git a/libctf/ctf-types.c b/libctf/ctf-types.c index 044a1852c1a5..d08d4542dc19 100644 --- a/libctf/ctf-types.c +++ b/libctf/ctf-types.c @@ -45,7 +45,7 @@ ctf_struct_member (ctf_dict_t *fp, ctf_lmember_t *dst, const ctf_type_t *tp, return -1; /* errno is set for us. */ /* Already large. */ - if (tp->ctt_size == CTF_LSIZE_SENT) + if (tp->ctt_size >= CTF_LSTRUCT_THRESH) { ctf_lmember_t *lmp = (ctf_lmember_t *) vlen; diff --git a/libctf/testsuite/lib/ctf-lib.exp b/libctf/testsuite/lib/ctf-lib.exp index 4df6619606b9..7175e14c30a2 100644 --- a/libctf/testsuite/lib/ctf-lib.exp +++ b/libctf/testsuite/lib/ctf-lib.exp @@ -141,6 +141,8 @@ proc compile_link_one_host_cc { src output additional_args } { # # link: # If set, link the SOURCE together even if only one file is specified. +# If "objects", keep the input object files and pass them as additional +# arguments to LOOKUP. # # link_flags: # If set, extra flags to pass to the linker. @@ -265,11 +267,14 @@ proc run_lookup_test { name } { # Compile the inputs and posibly link them together. set lookup_output "" + set objs {} if { [llength $opts(source)] > 0 } { set lookup_flags "" + set ld_flags "" if { $run_ld } { set lookup_output "tmpdir/out.so" - set lookup_flags "-gctf -fPIC $shared $opts(link_flags)" + set lookup_flags "-gctf -fPIC" + set ld_flags "$shared $opts(link_flags)" } else { set lookup_output "tmpdir/out.o" set lookup_flags "-gctf -fPIC -c" @@ -280,19 +285,38 @@ proc run_lookup_test { name } { if [board_info [target_info name] exists ldflags] { append lookup_flags " [board_info [target_info name] ldflags]" } - set src {} + set objsrcs {} + set local_srcs {} foreach sfile $opts(source) { + set local_src [file join [file dirname $file] $sfile] + lappend local_srcs $local_src + if [is_remote host] { - lappend src [remote_download host [file join [file dirname $file] $sfile]] + set src [remote_download host [file join [file dirname $file] $sfile]] + } else { + set src [file join [file dirname $file] $sfile] + } + + if { $opts(link) == "objects" } { + set obj "[file rootname $src].o" + set comp_output [prune_warnings [run_host_cmd "$CC_FOR_TARGET" "$CFLAGS_FOR_TARGET $lookup_flags $src -c -o $obj"]] + + if { $comp_output != ""} { + send_log "compilation of CTF program $local_src failed with <$comp_output>" + fail $testname + return 0 + } + lappend objsrcs $obj + lappend objs $obj } else { - lappend src [file join [file dirname $file] $sfile] + lappend objsrcs $src } } - set comp_output [prune_warnings [run_host_cmd "$CC_FOR_TARGET" "$CFLAGS_FOR_TARGET $lookup_flags [concat $src] -o $lookup_output"]] + set comp_output [prune_warnings [run_host_cmd "$CC_FOR_TARGET" "$CFLAGS_FOR_TARGET $lookup_flags $ld_flags [concat $objsrcs] -o $lookup_output"]] if { $comp_output != ""} { - send_log "compilation of CTF program [concat $src] failed with <$comp_output>" + send_log "compilation of CTF program [concat $local_srcs] failed with <$comp_output>" fail $testname return 0 } @@ -306,12 +330,13 @@ proc run_lookup_test { name } { } } - # Invoke the lookup program on the outputs, possibly through the wrapper. + # Invoke the lookup program on the outputs, possibly through the wrapper, including all + # the object file names if they were filled out. if { [llength $opts(wrapper)] == 0 } { - set results [run_host_cmd tmpdir/lookup $lookup_output] + set results [run_host_cmd tmpdir/lookup "$lookup_output $objs"] } else { - set results [run_host_cmd "$opts(wrapper) tmpdir/lookup" $lookup_output] + set results [run_host_cmd "$opts(wrapper) tmpdir/lookup" "$lookup_output $objs"] } if { [regexp {^UNSUPPORTED: (.*)$} $results -> reason] } { diff --git a/libctf/testsuite/libctf-lookup/big-struct-corruption.c b/libctf/testsuite/libctf-lookup/big-struct-corruption.c new file mode 100644 index 000000000000..2cc05be79cc1 --- /dev/null +++ b/libctf/testsuite/libctf-lookup/big-struct-corruption.c @@ -0,0 +1,118 @@ +/* Determine whether libctf/33339 is fixed, if and only if GCC PR 121411 is also + fixed. */ + +#include "config.h" +#include +#include +#include +#include + +/* Determine whether the passed-in struct's member's offsets ever descend. */ +static int +offsets_ascending (ctf_dict_t *fp, ctf_id_t type) +{ + ctf_next_t *it = NULL; + ssize_t offset, last_offset = 0; + + while ((offset = ctf_member_next (fp, type, &it, NULL, NULL, 0)) >= 0) + { + if (offset < last_offset) + return 0; + last_offset = offset; + } + if (ctf_errno (fp) != ECTF_NEXT_END) + { + fprintf (stderr, "Cannot check member offsets: %s\n", + ctf_errmsg (ctf_errno (fp))); + exit (0); + } + + return 1; +} + +int +main (int argc, char *argv[]) +{ + ctf_archive_t *ctf; + ctf_dict_t *fp; + ctf_id_t type; + int err; + + if (argc != 3) + { + fprintf (stderr, "Syntax: %s PROGRAM OBJ\n", argv[0]); + exit(1); + } + + /* Check for bugginess of compiler. */ + + if ((ctf = ctf_open (argv[2], NULL, &err)) == NULL) + { + fprintf (stderr, "Cannot open compiler object file %s: %s\n", + argv[2], ctf_errmsg (err)); + exit (1); + } + + /* Verify that offsets only ascend. */ + + if ((fp = ctf_arc_lookup_symbol_name (ctf, "huge_used", &type, &err)) == NULL) + { + fprintf (stderr, "UNSUPPORTED: compiler does not provide expected symbol.\n"); + exit (0); + } + + if (!offsets_ascending (fp, type)) + { + fprintf (stderr, "UNSUPPORTED: GCC bug PR121411 detected.\n"); + exit (0); + } + + ctf_dict_close (fp); + ctf_close (ctf); + + /* Check if test is disabled (e.g. on 32-bit). */ + + if ((ctf = ctf_open (argv[1], NULL, &err)) == NULL) + { + fprintf (stderr, "Cannot open linked binary test file %s: %s\n", + argv[1], ctf_errmsg (err)); + exit (1); + } + + if ((fp = ctf_arc_lookup_symbol_name (ctf, "test_disabled", &type, &err)) != NULL) + { + fprintf (stderr, "UNSUPPORTED: test not necessary on 32-bit targets.\n"); + exit (0); + } + + if ((fp = ctf_arc_lookup_symbol_name (ctf, "big_used", &type, &err)) == NULL) + { + fprintf (stderr, "big struct symbol not found.\n"); + exit (1); + } + + if (!offsets_ascending (fp, type)) + { + fprintf (stderr, "large struct offsets incorrect.\n"); + exit (1); + } + ctf_dict_close (fp); + + if ((fp = ctf_arc_lookup_symbol_name (ctf, "huge_used", &type, &err)) == NULL) + { + fprintf (stderr, "huge struct symbol not found.\n"); + exit (1); + } + + if (!offsets_ascending (fp, type)) + { + fprintf (stderr, "huge struct offsets incorrect.\n"); + exit (1); + } + + ctf_dict_close (fp); + ctf_close (ctf); + + fprintf (stderr, "Large and huge structs working fine.\n"); + exit (0); +} diff --git a/libctf/testsuite/libctf-lookup/big-struct-corruption.lk b/libctf/testsuite/libctf-lookup/big-struct-corruption.lk new file mode 100644 index 000000000000..980cb0bcecc8 --- /dev/null +++ b/libctf/testsuite/libctf-lookup/big-struct-corruption.lk @@ -0,0 +1,3 @@ +# source: big-struct-ctf.c +# link: objects +Large and huge structs working fine. diff --git a/libctf/testsuite/libctf-lookup/big-struct-ctf.c b/libctf/testsuite/libctf-lookup/big-struct-ctf.c new file mode 100644 index 000000000000..fc99a3e58789 --- /dev/null +++ b/libctf/testsuite/libctf-lookup/big-struct-ctf.c @@ -0,0 +1,72 @@ +#if defined (__SIZEOF_PTRDIFF_T__) && __SIZEOF_PTRDIFF_T__ > 4 + +#define CONCAT_(a,b) a ## b +#define CONCAT(a,b) CONCAT_(a, b) +#define COUNT(name) CONCAT(name, __COUNTER__) +#define MEMBNAME const char COUNT(memb)[1024 * 1024] +#define MEMB10 \ + MEMBNAME; \ + MEMBNAME; \ + MEMBNAME; \ + MEMBNAME; \ + MEMBNAME; \ + MEMBNAME; \ + MEMBNAME; \ + MEMBNAME; \ + MEMBNAME; \ + MEMBNAME; + +#define MEMB100 \ + MEMB10 \ + MEMB10 \ + MEMB10 \ + MEMB10 \ + MEMB10 \ + MEMB10 \ + MEMB10 \ + MEMB10 \ + MEMB10 \ + MEMB10 + +#define MEMB1000 \ + MEMB100 \ + MEMB100 \ + MEMB100 \ + MEMB100 \ + MEMB100 \ + MEMB100 \ + MEMB100 \ + MEMB100 \ + MEMB100 \ + MEMB100 + +#define MEMB10000 \ + MEMB1000 \ + MEMB1000 \ + MEMB1000 \ + MEMB1000 \ + MEMB1000 \ + MEMB1000 \ + MEMB1000 \ + MEMB1000 \ + MEMB1000 \ + MEMB1000 + +struct big +{ + MEMB1000; +}; + +struct huge +{ + MEMB10000; +}; + +struct big big_used; +struct huge huge_used; + +#else + +int test_disabled; + +#endif From 47111cfd4f971a7c56a7400f0709b3b51eeb8ef4 Mon Sep 17 00:00:00 2001 From: Bruce McCulloch Date: Fri, 1 Nov 2024 12:31:46 -0400 Subject: [PATCH 2/2] libctf: dump CTF array dimensions in the right order Before GCC PR114186, all looked good in the land of multidimensional arrays: you wrote int a[5][10]; and ctf_type_aname() et al would print it as int [5][10] Unfortunately this was two bugs in one. GCC was emitting the array as if it were int a[10][5], i.e. as this: a -> [10] -> [5] -> int rather than a -> [5] -> [10] -> int as it should be. libctf was hiding this by printing them in the wrong order, concealing the bug from anyone using objdump --ctf or anything but actual type graph traversal. Once this was fixed for GCC, the bug was visible in libctf: multidimensional arrays were printed backwards! (But this is just a print-time bug: the underlying bug, that something traversing the type graph would see the array in backwards order, was fixed by the fix to GCC.) Fix this libctf bug, printing the arrays the right way round. In a possibly futile attempt to retain some vestige of backwards compatibility, introduce a new bug-compat flag CTF_F_ARRNELEMS, which, if on, indicates that PR114186 is fixed and GCC is emitting array elements the right way round. (Unfortunately, the fix went in without this flag, so some GCCs will still emit CTF that will cause libctf to print them wrong, even with this fix -- but it's no wronger than it was before, and new GCC and new binutils, as well as GCC older than any fix for PR114186 and new binutils, will print things properly. Someone traversing the type graph will see things right after the GCC fix, wrong before it, and there isn't really any reliable way to tell which you have, though if CTF_F_ARRNELEMS is set, you definitely have a fixed GCC. The test checks for this, but it's not something we expect actual users to ever do -- CTF dict flags are an internal implementation detail with no user-visible API for a reason.) [nca: log message, test compat with older compilers] include/ * ctf.h (CTF_F_ARRNELEMS): New bug-compat flag. (CTF_F_MAX): Adjust. libctf/ PR libctf/32161 * ctf-decl.c (ctf_decl_push): Prepend if this is an array and the bug-compat flag is set. * ctf-dump.c (ctf_dump_header): Dump the new bug-compat flag. * testsuite/libctf-lookup/multidim-array*: New test. --- include/ctf.h | 3 +- libctf/ctf-decl.c | 7 +- libctf/ctf-dump.c | 8 ++- .../libctf-lookup/multidim-array-ctf.c | 3 + .../testsuite/libctf-lookup/multidim-array.c | 71 +++++++++++++++++++ .../testsuite/libctf-lookup/multidim-array.lk | 9 +++ 6 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 libctf/testsuite/libctf-lookup/multidim-array-ctf.c create mode 100644 libctf/testsuite/libctf-lookup/multidim-array.c create mode 100644 libctf/testsuite/libctf-lookup/multidim-array.lk diff --git a/include/ctf.h b/include/ctf.h index 72a639ac9b43..819003ed9113 100644 --- a/include/ctf.h +++ b/include/ctf.h @@ -213,8 +213,9 @@ typedef struct ctf_header #define CTF_F_NEWFUNCINFO 0x2 /* New v3 func info section format. */ #define CTF_F_IDXSORTED 0x4 /* Index sections already sorted. */ #define CTF_F_DYNSTR 0x8 /* Strings come from .dynstr. */ +#define CTF_F_ARRNELEMS 0x10 /* Array elems no longer reversed. */ #define CTF_F_MAX (CTF_F_COMPRESS | CTF_F_NEWFUNCINFO | CTF_F_IDXSORTED \ - | CTF_F_DYNSTR) + | CTF_F_DYNSTR | CTF_F_ARRNELEMS) typedef struct ctf_lblent { diff --git a/libctf/ctf-decl.c b/libctf/ctf-decl.c index 9e11913da86e..e5331b4e5d96 100644 --- a/libctf/ctf-decl.c +++ b/libctf/ctf-decl.c @@ -154,9 +154,12 @@ ctf_decl_push (ctf_decl_t *cd, ctf_dict_t *fp, ctf_id_t type) cd->cd_qualp = prec; /* By convention qualifiers of base types precede the type specifier (e.g. - const int vs. int const) even though the two forms are equivalent. */ + const int vs. int const) even though the two forms are equivalent. + As of gcc-14.2.0, arrays must also be prepended in order to dump with the + dimensions properly ordered. */ - if (is_qual && prec == CTF_PREC_BASE) + if ((is_qual && prec == CTF_PREC_BASE) || ((kind == CTF_K_ARRAY) && + (fp->ctf_openflags & (CTF_F_ARRNELEMS)))) ctf_list_prepend (&cd->cd_nodes[prec], cdp); else ctf_list_append (&cd->cd_nodes[prec], cdp); diff --git a/libctf/ctf-dump.c b/libctf/ctf-dump.c index 54febd644cd5..57fbd64b5306 100644 --- a/libctf/ctf-dump.c +++ b/libctf/ctf-dump.c @@ -326,7 +326,7 @@ ctf_dump_header (ctf_dict_t *fp, ctf_dump_state_t *state) if (fp->ctf_openflags > 0) { - if (asprintf (&flagstr, "%s%s%s%s%s%s%s", + if (asprintf (&flagstr, "%s%s%s%s%s%s%s%s%s", fp->ctf_openflags & CTF_F_COMPRESS ? "CTF_F_COMPRESS": "", (fp->ctf_openflags & CTF_F_COMPRESS) @@ -343,6 +343,12 @@ ctf_dump_header (ctf_dict_t *fp, ctf_dump_state_t *state) && (fp->ctf_openflags & ~(CTF_F_COMPRESS | CTF_F_NEWFUNCINFO | CTF_F_IDXSORTED)) ? ", " : "", + fp->ctf_openflags & CTF_F_ARRNELEMS + ? "CTF_F_ARRNELEMS" : "", + fp->ctf_openflags & (CTF_F_ARRNELEMS) + && (fp->ctf_openflags & ~(CTF_F_COMPRESS | CTF_F_NEWFUNCINFO + | CTF_F_IDXSORTED | CTF_F_ARRNELEMS)) + ? ", " : "", fp->ctf_openflags & CTF_F_DYNSTR ? "CTF_F_DYNSTR" : "") < 0) goto err; diff --git a/libctf/testsuite/libctf-lookup/multidim-array-ctf.c b/libctf/testsuite/libctf-lookup/multidim-array-ctf.c new file mode 100644 index 000000000000..05b6ebe4fddb --- /dev/null +++ b/libctf/testsuite/libctf-lookup/multidim-array-ctf.c @@ -0,0 +1,3 @@ +int a[3][5][9]; +int b[1][2]; + diff --git a/libctf/testsuite/libctf-lookup/multidim-array.c b/libctf/testsuite/libctf-lookup/multidim-array.c new file mode 100644 index 000000000000..2a86f2636c2a --- /dev/null +++ b/libctf/testsuite/libctf-lookup/multidim-array.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include + +int +main (int argc, char *argv[]) +{ + ctf_archive_t *ctf; + ctf_dict_t *fp; + int err; + ctf_dump_state_t *dump_state = NULL; + char *dumpstr; + ctf_next_t *it = NULL; + ctf_id_t type; + int flagged = 0; + + if ((ctf = ctf_open (argv[1], NULL, &err)) == NULL) + goto open_err; + if ((fp = ctf_dict_open (ctf, NULL, &err)) == NULL) + goto open_err; + + /* First, check for signs that the compiler is fixed but not emitting the + relevant flag yet. This combination is not expected to work right. */ + + while ((dumpstr = ctf_dump (fp, &dump_state, CTF_SECT_HEADER, + NULL, NULL)) != NULL) + { + if (strstr (dumpstr, "CTF_F_ARRNELEMS") != NULL) + flagged = 1; + free (dumpstr); + } + + if (!flagged) + { + ctf_arinfo_t ar; + + if ((type = ctf_lookup_by_symbol_name (fp, "a")) == CTF_ERR) + goto unexpected; + + if (ctf_array_info (fp, type, &ar) < 0) + goto unexpected; + + if (ar.ctr_nelems == 3) + { + fprintf (stderr, "UNSUPPORTED: compiler has GCC PR114186 fixed but " + "no indicative flag\n"); + return 0; + } + } + + /* Now check for the actual bug. */ + + while ((type = ctf_type_next (fp, &it, NULL, 1)) != -1) + printf ("%s\n", ctf_type_aname (fp, type)); + + ctf_dict_close (fp); + ctf_close (ctf); + + return 0; + + open_err: + fprintf (stderr, "%s: cannot open: %s\n", argv[0], ctf_errmsg (err)); + return 1; + + unexpected: + fprintf (stderr, "Cannot look up symbol to determine compiler bugginess: %s\n", + ctf_errmsg (ctf_errno (fp))); + return 1; +} + diff --git a/libctf/testsuite/libctf-lookup/multidim-array.lk b/libctf/testsuite/libctf-lookup/multidim-array.lk new file mode 100644 index 000000000000..20056f129eab --- /dev/null +++ b/libctf/testsuite/libctf-lookup/multidim-array.lk @@ -0,0 +1,9 @@ +# source: multidim-array-ctf.c +int +long unsigned int +int \[9\] +int \[5\]\[9\] +int \[3\]\[5\]\[9\] +int \[2\] +int \[1\]\[2\] +