* Re: [PATCH v2] gdb/dwarf2: Add symbols for function declarations
2025-07-03 19:45 [PATCH v2] gdb/dwarf2: Add symbols for function declarations Kevin Buettner
@ 2025-08-12 0:23 ` Kevin Buettner
2025-08-12 14:48 ` Simon Marchi
` (3 subsequent siblings)
4 siblings, 0 replies; 9+ messages in thread
From: Kevin Buettner @ 2025-08-12 0:23 UTC (permalink / raw)
To: gdb-patches
Ping.
On Thu, 3 Jul 2025 12:45:23 -0700
Kevin Buettner <kevinb@redhat.com> wrote:
> This commit was motivated by comments 3 and 4 for bug 31563:
>
> https://sourceware.org/bugzilla/show_bug.cgi?id=31563#c3
>
> When a program is built with -g3, macro information is available to
> GDB; for errno, the macro defined in /usr/include/errno.h (provided by
> GLIBC) looks like this:
>
> # define errno (*__errno_location ())
>
> However, up to now, GDB doesn't know the type of __errno_location,
> despite (sometimes) having a DIE representing a declaration providing
> its type. In any case, apparently not knowing the return type of
> __errno_location, GDB was unable to perform the inferior function call
> specified by the errno macro:
>
> (gdb) p errno
> '__errno_location' has unknown return type; cast the call to its
> declared return type
>
> But, for some compilers, GDB *should* be able to know the type. These
> are the DIEs related to the __errno_location declaration from the
> "macros" case for the gdb.base/errno.exp test:
>
> <1><37>: Abbrev Number: 2 (DW_TAG_subprogram)
> <38> DW_AT_external : 1
> <38> DW_AT_name : (indirect string, offset: 0x20e4):
> __errno_location
> <3c> DW_AT_decl_file : 2
> <3d> DW_AT_decl_line : 37
> <3e> DW_AT_decl_column : 13
> <3f> DW_AT_prototyped : 1
> <3f> DW_AT_type : <0x43>
> <43> DW_AT_declaration : 1
> <1><43>: Abbrev Number: 3 (DW_TAG_pointer_type)
> <44> DW_AT_byte_size : 8
> <45> DW_AT_type : <0x49>
> <1><49>: Abbrev Number: 4 (DW_TAG_base_type)
> <4a> DW_AT_byte_size : 4
> <4b> DW_AT_encoding : 5 (signed)
> <4c> DW_AT_name : int
>
> If you wish to see this for yourself, from your gdb build directory,
> do:
>
> make check TESTS=gdb.base/errno.exp
> readelf -w testsuite/outputs/gdb.base/errno/errno-macros | less
>
> With this commit in place, using gcc as the C compiler, 8 XFAILs in
> gdb.base/errno.exp turn into PASSes. They are:
>
> XFAIL: gdb.base/errno.exp: macros: print (int) errno
> XFAIL: gdb.base/errno.exp: macros: print errno
> XFAIL: gdb.base/errno.exp: pthreads-macros: print (int) errno
> XFAIL: gdb.base/errno.exp: pthreads-macros: print errno
> XFAIL: gdb.base/errno.exp: pthreads-static-macros: print (int) errno
> XFAIL: gdb.base/errno.exp: pthreads-static-macros: print errno
> XFAIL: gdb.base/errno.exp: static-macros: print (int) errno
> XFAIL: gdb.base/errno.exp: static-macros: print errno
>
> For the example shown earlier, GDB is now able to print the correct
> value for errno.
>
> As mentioned earlier, it doesn't work for all compliers. In
> particular, when clang is used instead, there's (currently) no change
> in results in the errno.exp test since clang doesn't provide the
> necessary declaration(s) in its DWARF output.
>
> Perhaps even more compelling is being able to call functions like
> malloc() without having debug info for the C library. To demonstrate
> this, I'll use the test program from gdb.base/break.exp. After
> starting the program (and not letting debuginfod fetch GLIBC's
> symbols), an unpatched GDB will show:
>
> (gdb) ptype malloc
> type = <unknown return type> ()
> (gdb) p malloc(4)
> 'malloc' has unknown return type; cast the call to its declared
> return type
>
> However, with this commit, we now see:
>
> (gdb) ptype malloc
> type = void *(unsigned long)
> (gdb) p malloc(4)
> $1 = (void *) 0x4042a0
>
> This commit changes the name of read_func_scope in gdb/dwarf2/read.c
> to read_func_scope_or_decl, changing all callers. I also added a
> comment for this function.
>
> It introduces a new function, die_is_func_decl_p and uses it in
> read_func_scope_or_decl(). If the call to die_is_func_decl_p()
> returns true, the code in read_func_scope_or_decl which attempts to
> get the function bounds is skipped and, after existing code which
> attempts to do some template related stuff happens, a new symbol with
> address class LOC_UNRESOLVED will be added.
>
> If just this change alone is made and regression testing is performed,
> there are quite a few regressions (well over 50, as I recall), mostly
> due to the fact that the PLT symbol / declaration is now found in
> various cases, perhaps ahead of the symbol for the function
> definition. I'll go into depth regarding the various cases, below.
>
> Many of the regressions were fixed by making the LOC_UNRESOLVED case
> in language_defn::read_var_value in gdb/findvar.c prefer "normal"
> symbols over PLT symbols, though the PLT symbol will be used if no
> normal symbol is found.
>
> This change contains a (perhaps) surprising addition to deal with GNU
> ifunc symbols:
>
> if (bmsym.minsym->type () == mst_text_gnu_ifunc)
> {
> /* GNU ifunc code elsewhere in GDB depends
> on the symbol's type being set as shown
> below. But, coming into this function,
> VAR might have an arguably better type
> obtained from a declaration, i.e.
> DW_AT_declaration. In this case, the
> PLT (solib trampoline) symbol is
> usually found first; see above.
> Nevertheless, we change the type to
> what the rest of GDB expects in order
> for the rest of the GNU ifunc related
> code in GDB to work. */
> type = builtin_type (objfile)
> ->nodebug_text_gnu_ifunc_symbol;
> }
>
> Hopefully, the comment adequately describes what this is about, but
> I'll note that without this particular bit of code, we see the
> following GNU ifunc related failures:
>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: gdb-command<p (int) gnu_ifunc (3)>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: gdb-command<p gnu_ifunc (3)>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: p gnu_ifunc executing
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: p gnu_ifunc()
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: resolver received HWCAP
>
> There are 17 more, but they're essentially repeats of the above, with
> varying resolver_attr, resolver_debug, and final_debug cases.
>
> The change to info_address_command in gdb/printcmd.c forces execution
> into the minimal symbol lookup case when presented with a
> LOC_UNRESOLVED function symbol. Without this change, there were 12
> falures in gdb.base/gnu-ifunc.exp, two of which look like this:
>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: info addr gnu_ifunc
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: info sym <gnu_ifunc-address>
>
> The remaining failures are similar, only differing in the values
> for resolver_attr, resolver_debug, and final_debug.
>
> With regard to the failure itself, for the first one, the log output
> looks like this:
>
> info addr gnu_ifunc
> Symbol "gnu_ifunc" is static storage at address 0x7ffff7fbb389.
> (gdb) FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: info addr gnu_ifunc
>
> The expected message from "info addr gnu_ifunc" was:
>
> Symbol "gnu_ifunc" is at 0x7ffff7fbb389 in a file compiled without
> debugging.
>
> I don't think that the FAILing message is wrong, but I think that the
> PASSing message (regarding being in a file without debugging) is more
> helpful to the user.
>
> It bothered me that the only tests which caught this problem were
> in gdb.base/gnu-ifunc.exp. There is now an "info addr foo" test
> in the new test case gdb.dwarf2/func-decl.exp which also performs
> this test.
>
> With the above change in place, we then see these failures:
>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=1:
> final_debug=0: info addr gnu_ifunc
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=1:
> final_debug=1: info addr gnu_ifunc
>
> (There are two others for "info sym <gnu_ifunc-address>".)
>
> In each case, we now see a message like this...
>
> Symbol "gnu_ifunc" is at 0x7ffff7fbb389 in a file compiled without
> debugging.
>
> ...when we should in fact see:
>
> Symbol "gnu_ifunc" is a function at address 0x7ffff7fbb389.
>
> Note that this is for the resolver_debug=1 case; for this case, the
> resolver library has symbols, so the latter message makes sense and
> the "failing" message is just plain wrong.
>
> These new failures are fixed by the change to
> lookup_global_or_static_symbol in gdb/symtab.c. In this change,
> normal function symbols are preferred to those whose address class is
> LOC_UNRESOLVED. I used a similar approach to that for
> language_defn::read_var_value, discussed earlier.
>
> Again, it seemed to me that there should be a non-gnu-ifunc test
> for this, so I added one; it'll be tested by:
>
> gdb.dwarf2/func-decl.exp: lib_debug: info addr foo
>
> There were also regressions in gdb.base/info-fun.exp and
> gdb.mi/mi-sym-info.exp:
>
> FAIL: gdb.base/info-fun.exp: n_flag=0: IN: info fun foo
> FAIL: gdb.base/info-fun.exp: n_flag=0: NO: info fun foo
> FAIL: gdb.base/info-fun.exp: n_flag=0: SEP: info fun foo
> FAIL: gdb.base/info-fun.exp: n_flag=1: IN: info fun -n foo
> FAIL: gdb.base/info-fun.exp: n_flag=1: NO: info fun -n foo
> FAIL: gdb.base/info-fun.exp: n_flag=1: SEP: info fun -n foo
> FAIL: gdb.mi/mi-sym-info.exp: List all functions matching pattern f3
> (unexpected output)
>
> For each of these failures, there was more output than expected. For
> example, for one of the failing cases...
>
> (gdb) info fun foo
> All functions matching regular expression "foo":
>
> File .../gdb/testsuite/gdb.base/info-fun.c:
> 16: int foo(void);
>
> Non-debugging symbols:
> 0x0000000000400370 foo@plt
> 0x00007ffff7fbb389 foo
> (gdb) FAIL: gdb.base/info-fun.exp: n_flag=0: NO: info fun foo
>
> The "passing" output looks like this:
>
> (gdb) info fun foo
> All functions matching regular expression "foo":
>
> Non-debugging symbols:
> 0x0000000000400370 foo@plt
> 0x00007ffff7fbb389 foo
> (gdb) PASS: gdb.base/info-fun.exp: n_flag=0: NO: info fun foo
>
> At first glance, the "failing" output looks useful; perhaps it could
> be, but I'll note that the extra lines being output are for a
> declaration for a function which is not in the CU where the function
> is defined. I have a hunch that we might be overwhelmed by extra
> output in a program with many libraries - it's conceivable that for
> some symbols, each library would have its own declaration.
>
> In any case, I was able to obtain the original / passing behavior
> by discarding LOC_UNRESOLVED symbols when searching in the
> function domain in global_symbol_searcher::add_matching_symbols.
>
> Finally, there were two regressions in gdb.base/completion.exp:
>
> FAIL: gdb.base/completion.exp: complete break break.c:ma
> FAIL: gdb.base/completion.exp: tab complete break break.c:ma (timeout)
>
> The log file for these failing tests is not especially helpful, but I
> debugged it by throwing a "gdb_interact" into the test to see what
> was going on. As I recall, when trying to complete "break.c:ma",
> "marker1", "marker2", "marker3", "marker4", and "malloc" were all
> being found in addition to "main", which is what the what the testcase
> was expecting to be the sole completion.
>
> This problem was fixed by adjusting completion_skip_symbol in
> symtab.h.
>
> The new test case, gdb.dwarf2/func-decl.exp, contains, in addition to
> the tests already discussed, two tests which will fail in a GDB built
> without this commit and pass in a GDB built with it...
>
> PASS: gdb.dwarf2/func-decl.exp: no_lib_debug:
> gdb-command<print foo ("abc", 5)>
> PASS: gdb.dwarf2/func-decl.exp: no_lib_debug: ptype foo
>
> The remaining tests in gdb.dwarf2/func-decl.exp should all pass in a
> GDB built with or without this commit. They will only fail if one of
> the relevant changes discussed above is missing or becomes broken for
> some reason (perhaps due to some future change to this area of the
> code).
>
> Regarding the use of the DWARF assembler in the test... Using some
> version(s) of GNU C, it's possible to write a test which causes a
> suitable declaration DIE to be placed in the DWARF output. In fact, I
> originally wrote most of the new test without the DWARF assembler.
> But not all compilers do this, e.g. clang does not, and I wanted a
> test which would test this functionality regardless of whether the
> compiler generates the DWARF required for this test.
>
> I've tested on Fedora 42 w/ architectures x86_64, aarch64, riscv,
> s390x, and ppc64le. On x86_64 Fedora 42, I've also tested with
> --target_board=unix/-m32, --target_board=native-gdbserver, and
> --target_board=native-extended-gdbserver. No regressions found.
>
> After skimming version 1 of this commit, Tom Tromey suggested that
> there should also be changes to the indexer. This version 2 commit
> adds that by making DW_TAG_subprogram declarations "interesting" to
> the indexer. The changes which do this are in gdb/dwarf2/abbrev.c
> and gdb/dwarf2/cooked-indexer.c. I also added a test to the new
> test case which attempts to do "ptype foo" prior to starting the
> program. This failed when using version 1 of this commit, but
> passes now.
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31563
> ---
> gdb/dwarf2/abbrev.c | 3 +
> gdb/dwarf2/cooked-indexer.c | 5 +
> gdb/dwarf2/read.c | 108 ++++++++++----
> gdb/findvar.c | 46 +++++-
> gdb/printcmd.c | 4 +-
> gdb/symtab.c | 43 ++++--
> gdb/symtab.h | 20 ++-
> gdb/testsuite/gdb.dwarf2/func-decl-lib.c | 24 +++
> gdb/testsuite/gdb.dwarf2/func-decl.c | 35 +++++
> gdb/testsuite/gdb.dwarf2/func-decl.exp | 182 +++++++++++++++++++++++
> 10 files changed, 427 insertions(+), 43 deletions(-)
> create mode 100644 gdb/testsuite/gdb.dwarf2/func-decl-lib.c
> create mode 100644 gdb/testsuite/gdb.dwarf2/func-decl.c
> create mode 100644 gdb/testsuite/gdb.dwarf2/func-decl.exp
>
> diff --git a/gdb/dwarf2/abbrev.c b/gdb/dwarf2/abbrev.c
> index 5cfff69cc3b..2ca80ab8a6a 100644
> --- a/gdb/dwarf2/abbrev.c
> +++ b/gdb/dwarf2/abbrev.c
> @@ -240,6 +240,9 @@ abbrev_table::read (struct dwarf2_section_info *section,
> the correct scope. */
> cur_abbrev->interesting = true;
> }
> + else if (has_hardcoded_declaration
> + && cur_abbrev->tag == DW_TAG_subprogram)
> + cur_abbrev->interesting = true;
> else if (has_hardcoded_declaration
> && (cur_abbrev->tag != DW_TAG_variable || !has_external))
> cur_abbrev->interesting = false;
> diff --git a/gdb/dwarf2/cooked-indexer.c b/gdb/dwarf2/cooked-indexer.c
> index c093984bae0..710ef82ed0d 100644
> --- a/gdb/dwarf2/cooked-indexer.c
> +++ b/gdb/dwarf2/cooked-indexer.c
> @@ -301,6 +301,11 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu,
> || abbrev->tag == DW_TAG_namespace)
> && abbrev->has_children)
> *flags |= IS_TYPE_DECLARATION;
> + else if (abbrev->tag == DW_TAG_subprogram)
> + {
> + /* We want to index function declarations - do nothing in order
> + to avoid nulling out *name, below. */
> + }
> else
> {
> *linkage_name = nullptr;
> diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
> index 5e18e452061..ea6f508d96e 100644
> --- a/gdb/dwarf2/read.c
> +++ b/gdb/dwarf2/read.c
> @@ -842,7 +842,7 @@ static void read_file_scope (struct die_info *, struct dwarf2_cu *);
>
> static void read_type_unit_scope (struct die_info *, struct dwarf2_cu *);
>
> -static void read_func_scope (struct die_info *, struct dwarf2_cu *);
> +static void read_func_scope_or_decl (struct die_info *, struct dwarf2_cu *);
>
> static void read_lexical_block_scope (struct die_info *, struct dwarf2_cu *);
>
> @@ -5012,7 +5012,7 @@ process_die (struct die_info *die, struct dwarf2_cu *cu)
> /* Fall through. */
> case DW_TAG_entry_point:
> case DW_TAG_inlined_subroutine:
> - read_func_scope (die, cu);
> + read_func_scope_or_decl (die, cu);
> break;
> case DW_TAG_lexical_block:
> case DW_TAG_try_block:
> @@ -8366,8 +8366,42 @@ fixup_low_high_pc (struct dwarf2_cu *cu, struct die_info *die, CORE_ADDR *low_pc
> }
> }
>
> +/* Return true if DIE represents a prototyped function declaration
> + with a return type and the function named NAME has a minimal
> + symbol in the CU's objfile. (NAME has already been extracted
> + from the DIE.) Return false otherwise. */
> +
> +static bool
> +die_is_func_decl_p (struct die_info *die, struct dwarf2_cu *cu,
> + const char * name)
> +{
> + if (die->tag != DW_TAG_subprogram)
> + return false;
> +
> + attribute *attr = dwarf2_attr (die, DW_AT_declaration, cu);
> + if (attr == nullptr || !attr->as_boolean ())
> + return false;
> +
> + attr = dwarf2_attr (die, DW_AT_type, cu);
> + if (attr == nullptr)
> + return false;
> +
> + attr = dwarf2_attr (die, DW_AT_prototyped, cu);
> + if (attr == nullptr || !attr->as_boolean ())
> + return false;
> +
> + bound_minimal_symbol mfunsym
> + = lookup_minimal_symbol (current_program_space, name,
> + cu->per_objfile->objfile);
> +
> + return (mfunsym.minsym != nullptr);
> +}
> +
> +/* Record symbol and related info for a function defined in
> + CU or for certain declarations (defined elsewhere). */
> +
> static void
> -read_func_scope (struct die_info *die, struct dwarf2_cu *cu)
> +read_func_scope_or_decl (struct die_info *die, struct dwarf2_cu *cu)
> {
> dwarf2_per_objfile *per_objfile = cu->per_objfile;
> struct objfile *objfile = per_objfile->objfile;
> @@ -8417,37 +8451,44 @@ read_func_scope (struct die_info *die, struct dwarf2_cu *cu)
> return;
> }
>
> - /* Ignore functions with missing or invalid low and high pc attributes. */
> - unrelocated_addr unrel_low, unrel_high;
> - if (dwarf2_get_pc_bounds (die, &unrel_low, &unrel_high, cu, nullptr, nullptr)
> - <= PC_BOUNDS_INVALID)
> + bool is_decl = die_is_func_decl_p (die, cu, name);
> +
> + /* If not a declaration, try to find the function bounds. */
> + if (!is_decl)
> {
> - if (have_complaint ())
> + /* Ignore functions with missing or invalid low and high pc
> + attributes. */
> + unrelocated_addr unrel_low, unrel_high;
> + if ((dwarf2_get_pc_bounds (die, &unrel_low, &unrel_high, cu,
> + nullptr, nullptr) <= PC_BOUNDS_INVALID))
> {
> - attr = dwarf2_attr (die, DW_AT_external, cu);
> - bool external_p = attr != nullptr && attr->as_boolean ();
> - attr = dwarf2_attr (die, DW_AT_inline, cu);
> - bool inlined_p = false;
> - if (attr != nullptr)
> + if (have_complaint ())
> {
> - std::optional<ULONGEST> value = attr->unsigned_constant ();
> - inlined_p = (value.has_value ()
> - && (*value == DW_INL_inlined
> - || *value == DW_INL_declared_inlined));
> + attr = dwarf2_attr (die, DW_AT_external, cu);
> + bool external_p = attr != nullptr && attr->as_boolean ();
> + attr = dwarf2_attr (die, DW_AT_inline, cu);
> + bool inlined_p = false;
> + if (attr != nullptr)
> + {
> + std::optional<ULONGEST> value = attr->unsigned_constant ();
> + inlined_p = (value.has_value ()
> + && (*value == DW_INL_inlined
> + || *value == DW_INL_declared_inlined));
> + }
> + attr = dwarf2_attr (die, DW_AT_declaration, cu);
> + bool decl_p = attr != nullptr && attr->as_boolean ();
> + if (!external_p && !inlined_p && !decl_p)
> + complaint (_("cannot get low and high bounds "
> + "for subprogram DIE at %s"),
> + sect_offset_str (die->sect_off));
> }
> - attr = dwarf2_attr (die, DW_AT_declaration, cu);
> - bool decl_p = attr != nullptr && attr->as_boolean ();
> - if (!external_p && !inlined_p && !decl_p)
> - complaint (_("cannot get low and high bounds "
> - "for subprogram DIE at %s"),
> - sect_offset_str (die->sect_off));
> + return;
> }
> - return;
> - }
>
> - lowpc = per_objfile->relocate (unrel_low);
> - highpc = per_objfile->relocate (unrel_high);
> - fixup_low_high_pc (cu, die, &lowpc, &highpc);
> + lowpc = per_objfile->relocate (unrel_low);
> + highpc = per_objfile->relocate (unrel_high);
> + fixup_low_high_pc (cu, die, &lowpc, &highpc);
> + }
>
> /* If we have any template arguments, then we must allocate a
> different sort of symbol. */
> @@ -8462,6 +8503,15 @@ read_func_scope (struct die_info *die, struct dwarf2_cu *cu)
> }
> }
>
> + /* If it's a declaration, record the symbol and return. */
> + if (is_decl)
> + {
> + struct symbol *sym =
> + new_symbol (die, read_type_die (die, cu), cu, templ_func);
> + sym->set_aclass_index (LOC_UNRESOLVED);
> + return;
> + }
> +
> gdb_assert (cu->get_builder () != nullptr);
> newobj = cu->get_builder ()->push_context (0, lowpc);
> newobj->name = new_symbol (die, read_type_die (die, cu), cu, templ_func);
> @@ -11259,7 +11309,7 @@ handle_struct_member_die (struct die_info *child_die, struct type *type,
> However, it does emit ordinary functions as children
> of a struct DIE. */
> if (cu->lang () == language_rust)
> - read_func_scope (child_die, cu);
> + read_func_scope_or_decl (child_die, cu);
> else
> {
> /* C++ member function. */
> diff --git a/gdb/findvar.c b/gdb/findvar.c
> index 9da5c4831a6..4934cf4c2a4 100644
> --- a/gdb/findvar.c
> +++ b/gdb/findvar.c
> @@ -444,19 +444,59 @@ language_defn::read_var_value (struct symbol *var,
> {
> struct obj_section *obj_section;
> bound_minimal_symbol bmsym;
> + bound_minimal_symbol bmsym_solib_tramp;
>
> gdbarch_iterate_over_objfiles_in_search_order
> (var->arch (),
> - [var, &bmsym] (objfile *objfile)
> + [var, &bmsym, &bmsym_solib_tramp, &type] (objfile *objfile)
> {
> bmsym = lookup_minimal_symbol (current_program_space,
> var->linkage_name (), objfile);
>
> - /* Stop if a match is found. */
> - return bmsym.minsym != nullptr;
> + if (bmsym.minsym != nullptr)
> + {
> + if (bmsym.minsym->type () == mst_solib_trampoline)
> + {
> + /* Stash the trampoline symbol in case no
> + better symbol is found. */
> + if (bmsym_solib_tramp.minsym == nullptr)
> + bmsym_solib_tramp = bmsym;
> + /* Keep searching... */
> + bmsym = {};
> + return false;
> + }
> + else
> + {
> + if (bmsym.minsym->type () == mst_text_gnu_ifunc)
> + {
> + /* GNU ifunc code elsewhere in GDB depends
> + on the symbol's type being set as shown
> + below. But, coming into this function,
> + VAR might have an arguably better type
> + obtained from a declaration, i.e.
> + DW_AT_declaration. In this case, the
> + PLT (solib trampoline) symbol is
> + usually found first; see above.
> + Nevertheless, we change the type to
> + what the rest of GDB expects in order
> + for the rest of the GNU ifunc related
> + code in GDB to work. */
> + type = builtin_type (objfile)
> + ->nodebug_text_gnu_ifunc_symbol;
> + }
> + return true;
> + }
> + }
> + else
> + return false;
> },
> var->objfile ());
>
> + /* Use the solib trampoline symbol if an alternative (non trampline)
> + symbol wasn't found. */
> + if (bmsym.minsym == nullptr)
> + bmsym = bmsym_solib_tramp;
> +
> /* If we can't find the minsym there's a problem in the symbol info.
> The symbol exists in the debug info, but it's missing in the minsym
> table. */
> diff --git a/gdb/printcmd.c b/gdb/printcmd.c
> index 19fbc20074e..30be62587bd 100644
> --- a/gdb/printcmd.c
> +++ b/gdb/printcmd.c
> @@ -1585,7 +1585,9 @@ info_address_command (const char *exp, int from_tty)
>
> sym = lookup_symbol (exp, get_selected_block (&context_pc), SEARCH_VFT,
> &is_a_field_of_this).symbol;
> - if (sym == NULL)
> + if (sym == NULL
> + || (sym->aclass () == LOC_UNRESOLVED
> + && symbol_is_function_or_method (sym)))
> {
> if (is_a_field_of_this.type != NULL)
> {
> diff --git a/gdb/symtab.c b/gdb/symtab.c
> index 7d1a0b066c7..e71837cb16e 100644
> --- a/gdb/symtab.c
> +++ b/gdb/symtab.c
> @@ -2654,15 +2654,33 @@ lookup_global_or_static_symbol (const char *name,
>
> /* Do a global search (of global blocks, heh). */
> if (result.symbol == NULL)
> - gdbarch_iterate_over_objfiles_in_search_order
> - (objfile != NULL ? objfile->arch () : current_inferior ()->arch (),
> - [&result, block_index, name, domain] (struct objfile *objfile_iter)
> - {
> - result = lookup_symbol_in_objfile (objfile_iter, block_index,
> - name, domain);
> - return result.symbol != nullptr;
> - },
> - objfile);
> + {
> + struct block_symbol result_unresolved = {};
> + gdbarch_iterate_over_objfiles_in_search_order
> + (objfile != NULL ? objfile->arch () : current_inferior ()->arch (),
> + [&result, &result_unresolved, block_index, name, domain]
> + (struct objfile *objfile_iter)
> + {
> + result = lookup_symbol_in_objfile (objfile_iter, block_index,
> + name, domain);
> + /* If RESULT is an unresolved function or method, keep track
> + of it in case no other symbols are found, but prefer
> + other matches over this one. */
> + if (result.symbol != nullptr
> + && result.symbol->aclass () == LOC_UNRESOLVED
> + && symbol_is_function_or_method (result.symbol))
> + {
> + result_unresolved = result;
> + result = {};
> + return false;
> + }
> + return result.symbol != nullptr;
> + },
> + objfile);
> +
> + if (result.symbol == nullptr)
> + result = result_unresolved;
> + }
>
> if (result.symbol != NULL)
> symbol_cache_mark_found (bsc, slot, objfile, result.symbol, result.block,
> @@ -5019,6 +5037,13 @@ global_symbol_searcher::add_matching_symbols
> && !treg_matches_sym_type_name (*treg, sym)))
> continue;
>
> + if ((kind & SEARCH_FUNCTION_DOMAIN) != 0)
> + {
> + /* Don't include unresolved function symbols. */
> + if (sym->aclass () == LOC_UNRESOLVED)
> + continue;
> + }
> +
> if ((kind & SEARCH_VAR_DOMAIN) != 0)
> {
> if (sym->aclass () == LOC_UNRESOLVED
> diff --git a/gdb/symtab.h b/gdb/symtab.h
> index 0a57be5ed80..461ca9d3a1e 100644
> --- a/gdb/symtab.h
> +++ b/gdb/symtab.h
> @@ -2494,6 +2494,23 @@ extern bool symbol_is_function_or_method (symbol *sym);
>
> extern bool symbol_is_function_or_method (minimal_symbol *msymbol);
>
> +/* Return whether SYM is an unresolved symbol. */
> +
> +static inline bool
> +completion_symbol_is_unresolved (symbol *sym)
> +{
> + return sym->aclass () == LOC_UNRESOLVED;
> +}
> +
> +/* For the purposes of skipping symbols for completion, return whether
> + MSYMBOL is unresolved. */
> +
> +static inline bool
> +completion_symbol_is_unresolved (minimal_symbol *msymbol)
> +{
> + return false;
> +}
> +
> /* Return whether SYM should be skipped in completion mode MODE. In
> linespec mode, we're only interested in functions/methods. */
>
> @@ -2502,7 +2519,8 @@ static bool
> completion_skip_symbol (complete_symbol_mode mode, Symbol *sym)
> {
> return (mode == complete_symbol_mode::LINESPEC
> - && !symbol_is_function_or_method (sym));
> + && (!symbol_is_function_or_method (sym)
> + || completion_symbol_is_unresolved (sym)));
> }
>
> /* symtab.c */
> diff --git a/gdb/testsuite/gdb.dwarf2/func-decl-lib.c b/gdb/testsuite/gdb.dwarf2/func-decl-lib.c
> new file mode 100644
> index 00000000000..4aad59be7ab
> --- /dev/null
> +++ b/gdb/testsuite/gdb.dwarf2/func-decl-lib.c
> @@ -0,0 +1,24 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> + Copyright 2025 Free Software Foundation, Inc.
> +
> + 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 <string.h>
> +
> +int
> +foo (char *s, int n)
> +{
> + return ((int) strlen (s) + n);
> +}
> diff --git a/gdb/testsuite/gdb.dwarf2/func-decl.c b/gdb/testsuite/gdb.dwarf2/func-decl.c
> new file mode 100644
> index 00000000000..62c49d5c456
> --- /dev/null
> +++ b/gdb/testsuite/gdb.dwarf2/func-decl.c
> @@ -0,0 +1,35 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> + Copyright 2025 Free Software Foundation, Inc.
> +
> + 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/>. */
> +
> +extern int foo (char *s, int n);
> +
> +volatile int data;
> +
> +void
> +use_it (int a)
> +{
> + data = a;
> +}
> +
> +int
> +main (int argc, char **argv)
> +{
> + asm ("main_label: .globl main_label");
> + int i;
> + i = foo ("foo", 2);
> + use_it (i);
> +}
> diff --git a/gdb/testsuite/gdb.dwarf2/func-decl.exp b/gdb/testsuite/gdb.dwarf2/func-decl.exp
> new file mode 100644
> index 00000000000..316dfe8d04c
> --- /dev/null
> +++ b/gdb/testsuite/gdb.dwarf2/func-decl.exp
> @@ -0,0 +1,182 @@
> +# Copyright 2025 Free Software Foundation, Inc.
> +# 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.
> +
> +# Test GDB's ability to access declarations for function symbols. On
> +# Linux, using GCC, compiling the main program with -g and the shared
> +# lib source file without -g can be used to create a compelling test
> +# case without needing the DWARF assembler. However, we don't want to
> +# count on the fact that the compiler will place a declaration for the
> +# shared library function in the DWARF info for the main program. (E.g.
> +# when using CLANG/LLVM, these DIEs are omitted.) Therefore, we use the
> +# DWARF assembler to create the requisite DWARF info for this test.
> +
> +load_lib dwarf.exp
> +require dwarf2_support
> +
> +standard_testfile .c -dw.S
> +
> +set asm_file [standard_output_file $srcfile2]
> +set libsrc "${srcdir}/${subdir}/${testfile}-lib.c"
> +set libobj [standard_output_file "${testfile}-lib.so"]
> +
> +# We need to know the size of integer and address types in order to
> +# write some of the debugging info we'd like to generate.
> +#
> +# For that, we ask GDB by debugging our test program. Any program
> +# would do, but since we already have program written specifically for
> +# this testcase, we might as well use that. Note that we need to
> +# also build the shared library that the test program uses.
> +
> +set session_options [list debug shlib=${libobj}]
> +if { [gdb_compile_shlib $libsrc $libobj [list nodebug]] != "" } {
> + untested "failed to compile shared object"
> + return -1
> +}
> +
> +if { [prepare_for_testing "failed to prepare" ${binfile} \
> + [list $srcfile] $session_options] } {
> + return -1
> +}
> +
> +with_test_prefix "first session" {
> + if ![runto_main] {
> + return
> + }
> +
> + with_shared_gdb {
> + # Rather than start a new session, declare the current session the
> + # shared one. Otherwise, get_func_info would compile an executable
> + # in a temp dir; due to implementation details, this means that the
> + # shared lib won't be found.
> + share_gdb ${srcdir}/${subdir}/$srcfile $session_options
> +
> + get_func_info main $session_options
> +
> + # Using the running GDB session, determine sizes of several types.
> + set int_size [get_sizeof "int" -1]
> + set char_ptr_size [get_sizeof "char *" 8]
> + }
> +}
> +
> +Dwarf::assemble $asm_file {
> + cu {} {
> + DW_TAG_compile_unit {
> + {DW_AT_language @DW_LANG_C99}
> + {DW_AT_name $::srcfile}
> + } {
> + declare_labels int_label char_label char_ptr_label
> +
> + int_label: DW_TAG_base_type {
> + {DW_AT_byte_size ${::int_size} DW_FORM_udata}
> + {DW_AT_encoding @DW_ATE_signed}
> + {DW_AT_name "int"}
> + }
> +
> + char_label: DW_TAG_base_type {
> + {byte_size 1 sdata}
> + {encoding @DW_ATE_signed_char}
> + {name "char"}
> + }
> +
> + char_ptr_label: DW_TAG_pointer_type {
> + {DW_AT_byte_size ${::char_ptr_size} DW_FORM_sdata}
> + {DW_AT_type :$char_label}
> + }
> +
> + DW_TAG_subprogram {
> + {DW_AT_external 1 flag}
> + {DW_AT_name foo}
> + {DW_AT_prototyped 1 DW_FORM_flag_present}
> + {DW_AT_type :$int_label}
> + {DW_AT_declaration 1 flag}
> + } {
> + DW_TAG_formal_parameter {
> + {DW_AT_type :$char_ptr_label}
> + }
> + DW_TAG_formal_parameter {
> + {DW_AT_type :$int_label}
> + }
> + }
> +
> + DW_TAG_subprogram {
> + {DW_AT_name main}
> + {DW_AT_low_pc ${::main_start} DW_FORM_addr}
> + {DW_AT_high_pc ${::main_end} DW_FORM_addr}
> + {DW_AT_type :$int_label}
> + }
> + }
> + }
> +}
> +
> +# Test against a shared library built with no debugging symbols. Due
> +# to the DWARF info provided by the DWARF assembler above, there will
> +# be a declaration for the shared lib symbol "foo" in the main
> +# program. Thus, due to the lack of DWARF info in the shared library,
> +# GDB can't know the type from the shared library. Instead, it must
> +# rely on the declaration of foo from the main program.
> +#
> +# Due to that declaration, it should be possible to examine its type
> +# as well as make an inferior function call. We expect "info addr foo"
> +# to provide the address of the actual function instead of foo's PLT
> +# in the main program.
> +
> +with_test_prefix no_lib_debug {
> + if { [gdb_compile_shlib $libsrc $libobj [list nodebug]] != "" } {
> + untested "failed to compile shared object"
> + return -1
> + }
> +
> + if { [prepare_for_testing "failed to prepare" ${binfile} \
> + [list $srcfile $asm_file] [list nodebug shlib=${libobj}]] } {
> + return -1
> + }
> +
> + with_test_prefix "before program start" {
> + # Verify that the type of foo is available prior to starting
> + # the program.
> + gdb_test "ptype foo" "^type = int \\(char \\*, int\\)"
> + }
> +
> + clean_restart $binfile
> +
> + if ![runto_main] {
> + return
> + }
> +
> + gdb_test "ptype foo" "^type = int \\(char \\*, int\\)"
> + gdb_test "print foo \(\"abc\", 5\)" "= 8"
> + gdb_test "info addr foo" "Symbol \"foo\" is at $::hex in a file compiled without debugging\\."
> +}
> +
> +# Test again with a library built with debugging symbols. The
> +# "info addr foo" test can fail if PLT symbols are preferred over
> +# normal symbols when looking up a global or static symbol.
> +
> +with_test_prefix lib_debug {
> + set binfile $binfile-debug
> + set libobj [standard_output_file "${testfile}-lib-debug.so"]
> +
> + if { [gdb_compile_shlib $libsrc $libobj [list debug]] != "" } {
> + untested "failed to compile shared object"
> + return -1
> + }
> +
> + if { [prepare_for_testing "failed to prepare" ${binfile} \
> + [list $srcfile $asm_file] [list nodebug shlib=${libobj}]] } {
> + return -1
> + }
> +
> + if ![runto_main] {
> + return
> + }
> +
> + gdb_test "info addr foo" "Symbol \"foo\" is a function at address $::hex\\."
> +}
> --
> 2.50.0
>
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v2] gdb/dwarf2: Add symbols for function declarations
2025-07-03 19:45 [PATCH v2] gdb/dwarf2: Add symbols for function declarations Kevin Buettner
2025-08-12 0:23 ` Kevin Buettner
@ 2025-08-12 14:48 ` Simon Marchi
2025-08-29 18:30 ` Andrew Burgess
` (2 subsequent siblings)
4 siblings, 0 replies; 9+ messages in thread
From: Simon Marchi @ 2025-08-12 14:48 UTC (permalink / raw)
To: Kevin Buettner, gdb-patches
On 7/3/25 3:45 PM, Kevin Buettner wrote:
> This commit was motivated by comments 3 and 4 for bug 31563:
>
> https://sourceware.org/bugzilla/show_bug.cgi?id=31563#c3
>
> When a program is built with -g3, macro information is available to
> GDB; for errno, the macro defined in /usr/include/errno.h (provided by
> GLIBC) looks like this:
>
> # define errno (*__errno_location ())
>
> However, up to now, GDB doesn't know the type of __errno_location,
> despite (sometimes) having a DIE representing a declaration providing
> its type. In any case, apparently not knowing the return type of
> __errno_location, GDB was unable to perform the inferior function call
> specified by the errno macro:
>
> (gdb) p errno
> '__errno_location' has unknown return type; cast the call to its
> declared return type
>
> But, for some compilers, GDB *should* be able to know the type. These
> are the DIEs related to the __errno_location declaration from the
> "macros" case for the gdb.base/errno.exp test:
>
> <1><37>: Abbrev Number: 2 (DW_TAG_subprogram)
> <38> DW_AT_external : 1
> <38> DW_AT_name : (indirect string, offset: 0x20e4):
> __errno_location
> <3c> DW_AT_decl_file : 2
> <3d> DW_AT_decl_line : 37
> <3e> DW_AT_decl_column : 13
> <3f> DW_AT_prototyped : 1
> <3f> DW_AT_type : <0x43>
> <43> DW_AT_declaration : 1
> <1><43>: Abbrev Number: 3 (DW_TAG_pointer_type)
> <44> DW_AT_byte_size : 8
> <45> DW_AT_type : <0x49>
> <1><49>: Abbrev Number: 4 (DW_TAG_base_type)
> <4a> DW_AT_byte_size : 4
> <4b> DW_AT_encoding : 5 (signed)
> <4c> DW_AT_name : int
>
> If you wish to see this for yourself, from your gdb build directory,
> do:
>
> make check TESTS=gdb.base/errno.exp
> readelf -w testsuite/outputs/gdb.base/errno/errno-macros | less
>
> With this commit in place, using gcc as the C compiler, 8 XFAILs in
> gdb.base/errno.exp turn into PASSes. They are:
>
> XFAIL: gdb.base/errno.exp: macros: print (int) errno
> XFAIL: gdb.base/errno.exp: macros: print errno
> XFAIL: gdb.base/errno.exp: pthreads-macros: print (int) errno
> XFAIL: gdb.base/errno.exp: pthreads-macros: print errno
> XFAIL: gdb.base/errno.exp: pthreads-static-macros: print (int) errno
> XFAIL: gdb.base/errno.exp: pthreads-static-macros: print errno
> XFAIL: gdb.base/errno.exp: static-macros: print (int) errno
> XFAIL: gdb.base/errno.exp: static-macros: print errno
>
> For the example shown earlier, GDB is now able to print the correct
> value for errno.
>
> As mentioned earlier, it doesn't work for all compliers. In
> particular, when clang is used instead, there's (currently) no change
> in results in the errno.exp test since clang doesn't provide the
> necessary declaration(s) in its DWARF output.
>
> Perhaps even more compelling is being able to call functions like
> malloc() without having debug info for the C library. To demonstrate
> this, I'll use the test program from gdb.base/break.exp. After
> starting the program (and not letting debuginfod fetch GLIBC's
> symbols), an unpatched GDB will show:
>
> (gdb) ptype malloc
> type = <unknown return type> ()
> (gdb) p malloc(4)
> 'malloc' has unknown return type; cast the call to its declared
> return type
>
> However, with this commit, we now see:
>
> (gdb) ptype malloc
> type = void *(unsigned long)
> (gdb) p malloc(4)
> $1 = (void *) 0x4042a0
>
> This commit changes the name of read_func_scope in gdb/dwarf2/read.c
> to read_func_scope_or_decl, changing all callers. I also added a
> comment for this function.
>
> It introduces a new function, die_is_func_decl_p and uses it in
> read_func_scope_or_decl(). If the call to die_is_func_decl_p()
> returns true, the code in read_func_scope_or_decl which attempts to
> get the function bounds is skipped and, after existing code which
> attempts to do some template related stuff happens, a new symbol with
> address class LOC_UNRESOLVED will be added.
>
> If just this change alone is made and regression testing is performed,
> there are quite a few regressions (well over 50, as I recall), mostly
> due to the fact that the PLT symbol / declaration is now found in
> various cases, perhaps ahead of the symbol for the function
> definition. I'll go into depth regarding the various cases, below.
>
> Many of the regressions were fixed by making the LOC_UNRESOLVED case
> in language_defn::read_var_value in gdb/findvar.c prefer "normal"
> symbols over PLT symbols, though the PLT symbol will be used if no
> normal symbol is found.
>
> This change contains a (perhaps) surprising addition to deal with GNU
> ifunc symbols:
>
> if (bmsym.minsym->type () == mst_text_gnu_ifunc)
> {
> /* GNU ifunc code elsewhere in GDB depends
> on the symbol's type being set as shown
> below. But, coming into this function,
> VAR might have an arguably better type
> obtained from a declaration, i.e.
> DW_AT_declaration. In this case, the
> PLT (solib trampoline) symbol is
> usually found first; see above.
> Nevertheless, we change the type to
> what the rest of GDB expects in order
> for the rest of the GNU ifunc related
> code in GDB to work. */
> type = builtin_type (objfile)
> ->nodebug_text_gnu_ifunc_symbol;
> }
>
> Hopefully, the comment adequately describes what this is about, but
> I'll note that without this particular bit of code, we see the
> following GNU ifunc related failures:
>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: gdb-command<p (int) gnu_ifunc (3)>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: gdb-command<p gnu_ifunc (3)>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: p gnu_ifunc executing
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: p gnu_ifunc()
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: resolver received HWCAP
>
> There are 17 more, but they're essentially repeats of the above, with
> varying resolver_attr, resolver_debug, and final_debug cases.
>
> The change to info_address_command in gdb/printcmd.c forces execution
> into the minimal symbol lookup case when presented with a
> LOC_UNRESOLVED function symbol. Without this change, there were 12
> falures in gdb.base/gnu-ifunc.exp, two of which look like this:
>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: info addr gnu_ifunc
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: info sym <gnu_ifunc-address>
>
> The remaining failures are similar, only differing in the values
> for resolver_attr, resolver_debug, and final_debug.
>
> With regard to the failure itself, for the first one, the log output
> looks like this:
>
> info addr gnu_ifunc
> Symbol "gnu_ifunc" is static storage at address 0x7ffff7fbb389.
> (gdb) FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: info addr gnu_ifunc
>
> The expected message from "info addr gnu_ifunc" was:
>
> Symbol "gnu_ifunc" is at 0x7ffff7fbb389 in a file compiled without
> debugging.
>
> I don't think that the FAILing message is wrong, but I think that the
> PASSing message (regarding being in a file without debugging) is more
> helpful to the user.
>
> It bothered me that the only tests which caught this problem were
> in gdb.base/gnu-ifunc.exp. There is now an "info addr foo" test
> in the new test case gdb.dwarf2/func-decl.exp which also performs
> this test.
>
> With the above change in place, we then see these failures:
>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=1:
> final_debug=0: info addr gnu_ifunc
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=1:
> final_debug=1: info addr gnu_ifunc
>
> (There are two others for "info sym <gnu_ifunc-address>".)
>
> In each case, we now see a message like this...
>
> Symbol "gnu_ifunc" is at 0x7ffff7fbb389 in a file compiled without
> debugging.
>
> ...when we should in fact see:
>
> Symbol "gnu_ifunc" is a function at address 0x7ffff7fbb389.
>
> Note that this is for the resolver_debug=1 case; for this case, the
> resolver library has symbols, so the latter message makes sense and
> the "failing" message is just plain wrong.
>
> These new failures are fixed by the change to
> lookup_global_or_static_symbol in gdb/symtab.c. In this change,
> normal function symbols are preferred to those whose address class is
> LOC_UNRESOLVED. I used a similar approach to that for
> language_defn::read_var_value, discussed earlier.
>
> Again, it seemed to me that there should be a non-gnu-ifunc test
> for this, so I added one; it'll be tested by:
>
> gdb.dwarf2/func-decl.exp: lib_debug: info addr foo
>
> There were also regressions in gdb.base/info-fun.exp and
> gdb.mi/mi-sym-info.exp:
>
> FAIL: gdb.base/info-fun.exp: n_flag=0: IN: info fun foo
> FAIL: gdb.base/info-fun.exp: n_flag=0: NO: info fun foo
> FAIL: gdb.base/info-fun.exp: n_flag=0: SEP: info fun foo
> FAIL: gdb.base/info-fun.exp: n_flag=1: IN: info fun -n foo
> FAIL: gdb.base/info-fun.exp: n_flag=1: NO: info fun -n foo
> FAIL: gdb.base/info-fun.exp: n_flag=1: SEP: info fun -n foo
> FAIL: gdb.mi/mi-sym-info.exp: List all functions matching pattern f3
> (unexpected output)
>
> For each of these failures, there was more output than expected. For
> example, for one of the failing cases...
>
> (gdb) info fun foo
> All functions matching regular expression "foo":
>
> File .../gdb/testsuite/gdb.base/info-fun.c:
> 16: int foo(void);
>
> Non-debugging symbols:
> 0x0000000000400370 foo@plt
> 0x00007ffff7fbb389 foo
> (gdb) FAIL: gdb.base/info-fun.exp: n_flag=0: NO: info fun foo
>
> The "passing" output looks like this:
>
> (gdb) info fun foo
> All functions matching regular expression "foo":
>
> Non-debugging symbols:
> 0x0000000000400370 foo@plt
> 0x00007ffff7fbb389 foo
> (gdb) PASS: gdb.base/info-fun.exp: n_flag=0: NO: info fun foo
>
> At first glance, the "failing" output looks useful; perhaps it could
> be, but I'll note that the extra lines being output are for a
> declaration for a function which is not in the CU where the function
> is defined. I have a hunch that we might be overwhelmed by extra
> output in a program with many libraries - it's conceivable that for
> some symbols, each library would have its own declaration.
>
> In any case, I was able to obtain the original / passing behavior
> by discarding LOC_UNRESOLVED symbols when searching in the
> function domain in global_symbol_searcher::add_matching_symbols.
>
> Finally, there were two regressions in gdb.base/completion.exp:
>
> FAIL: gdb.base/completion.exp: complete break break.c:ma
> FAIL: gdb.base/completion.exp: tab complete break break.c:ma (timeout)
>
> The log file for these failing tests is not especially helpful, but I
> debugged it by throwing a "gdb_interact" into the test to see what
> was going on. As I recall, when trying to complete "break.c:ma",
> "marker1", "marker2", "marker3", "marker4", and "malloc" were all
> being found in addition to "main", which is what the what the testcase
> was expecting to be the sole completion.
>
> This problem was fixed by adjusting completion_skip_symbol in
> symtab.h.
>
> The new test case, gdb.dwarf2/func-decl.exp, contains, in addition to
> the tests already discussed, two tests which will fail in a GDB built
> without this commit and pass in a GDB built with it...
>
> PASS: gdb.dwarf2/func-decl.exp: no_lib_debug:
> gdb-command<print foo ("abc", 5)>
> PASS: gdb.dwarf2/func-decl.exp: no_lib_debug: ptype foo
>
> The remaining tests in gdb.dwarf2/func-decl.exp should all pass in a
> GDB built with or without this commit. They will only fail if one of
> the relevant changes discussed above is missing or becomes broken for
> some reason (perhaps due to some future change to this area of the
> code).
>
> Regarding the use of the DWARF assembler in the test... Using some
> version(s) of GNU C, it's possible to write a test which causes a
> suitable declaration DIE to be placed in the DWARF output. In fact, I
> originally wrote most of the new test without the DWARF assembler.
> But not all compilers do this, e.g. clang does not, and I wanted a
> test which would test this functionality regardless of whether the
> compiler generates the DWARF required for this test.
>
> I've tested on Fedora 42 w/ architectures x86_64, aarch64, riscv,
> s390x, and ppc64le. On x86_64 Fedora 42, I've also tested with
> --target_board=unix/-m32, --target_board=native-gdbserver, and
> --target_board=native-extended-gdbserver. No regressions found.
>
> After skimming version 1 of this commit, Tom Tromey suggested that
> there should also be changes to the indexer. This version 2 commit
> adds that by making DW_TAG_subprogram declarations "interesting" to
> the indexer. The changes which do this are in gdb/dwarf2/abbrev.c
> and gdb/dwarf2/cooked-indexer.c. I also added a test to the new
> test case which attempts to do "ptype foo" prior to starting the
> program. This failed when using version 1 of this commit, but
> passes now.
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31563
Wow, this is a lot of info to unpack, I read through it and haven't
recovered yet :P. I will just look at the test case for now, and I
might do a later pass with a fresh head.
I just have one minor suggestion in the test:
> +# Test against a shared library built with no debugging symbols. Due
> +# to the DWARF info provided by the DWARF assembler above, there will
> +# be a declaration for the shared lib symbol "foo" in the main
> +# program. Thus, due to the lack of DWARF info in the shared library,
> +# GDB can't know the type from the shared library. Instead, it must
> +# rely on the declaration of foo from the main program.
> +#
> +# Due to that declaration, it should be possible to examine its type
> +# as well as make an inferior function call. We expect "info addr foo"
> +# to provide the address of the actual function instead of foo's PLT
> +# in the main program.
> +
> +with_test_prefix no_lib_debug {
> + if { [gdb_compile_shlib $libsrc $libobj [list nodebug]] != "" } {
> + untested "failed to compile shared object"
> + return -1
> + }
> +
> + if { [prepare_for_testing "failed to prepare" ${binfile} \
> + [list $srcfile $asm_file] [list nodebug shlib=${libobj}]] } {
> + return -1
> + }
> +
> + with_test_prefix "before program start" {
> + # Verify that the type of foo is available prior to starting
> + # the program.
> + gdb_test "ptype foo" "^type = int \\(char \\*, int\\)"
> + }
> +
> + clean_restart $binfile
> +
> + if ![runto_main] {
> + return
> + }
> +
> + gdb_test "ptype foo" "^type = int \\(char \\*, int\\)"
> + gdb_test "print foo \(\"abc\", 5\)" "= 8"
> + gdb_test "info addr foo" "Symbol \"foo\" is at $::hex in a file compiled without debugging\\."
> +}
> +
> +# Test again with a library built with debugging symbols. The
> +# "info addr foo" test can fail if PLT symbols are preferred over
> +# normal symbols when looking up a global or static symbol.
> +
> +with_test_prefix lib_debug {
> + set binfile $binfile-debug
> + set libobj [standard_output_file "${testfile}-lib-debug.so"]
> +
> + if { [gdb_compile_shlib $libsrc $libobj [list debug]] != "" } {
> + untested "failed to compile shared object"
> + return -1
> + }
> +
> + if { [prepare_for_testing "failed to prepare" ${binfile} \
> + [list $srcfile $asm_file] [list nodebug shlib=${libobj}]] } {
> + return -1
> + }
> +
> + if ![runto_main] {
> + return
> + }
> +
> + gdb_test "info addr foo" "Symbol \"foo\" is a function at address $::hex\\."
> +}
I would suggest doing a:
foreach_with_prefix lib_debug {nodebug debug} {
...
}
to test both scenarios. There will be less duplication, and all
commands will get tested in both modes, for free. I think you will just
need one if/else for the "info addr foo" output.
Simon
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v2] gdb/dwarf2: Add symbols for function declarations
2025-07-03 19:45 [PATCH v2] gdb/dwarf2: Add symbols for function declarations Kevin Buettner
2025-08-12 0:23 ` Kevin Buettner
2025-08-12 14:48 ` Simon Marchi
@ 2025-08-29 18:30 ` Andrew Burgess
2025-09-05 15:24 ` Andrew Burgess
2025-09-05 15:38 ` Tom Tromey
4 siblings, 0 replies; 9+ messages in thread
From: Andrew Burgess @ 2025-08-29 18:30 UTC (permalink / raw)
To: Kevin Buettner, gdb-patches; +Cc: Kevin Buettner
Kevin Buettner <kevinb@redhat.com> writes:
> This commit was motivated by comments 3 and 4 for bug 31563:
>
> https://sourceware.org/bugzilla/show_bug.cgi?id=31563#c3
>
> When a program is built with -g3, macro information is available to
> GDB; for errno, the macro defined in /usr/include/errno.h (provided by
> GLIBC) looks like this:
>
> # define errno (*__errno_location ())
>
> However, up to now, GDB doesn't know the type of __errno_location,
> despite (sometimes) having a DIE representing a declaration providing
> its type. In any case, apparently not knowing the return type of
> __errno_location, GDB was unable to perform the inferior function call
> specified by the errno macro:
>
> (gdb) p errno
> '__errno_location' has unknown return type; cast the call to its
> declared return type
>
> But, for some compilers, GDB *should* be able to know the type. These
> are the DIEs related to the __errno_location declaration from the
> "macros" case for the gdb.base/errno.exp test:
>
> <1><37>: Abbrev Number: 2 (DW_TAG_subprogram)
> <38> DW_AT_external : 1
> <38> DW_AT_name : (indirect string, offset: 0x20e4):
> __errno_location
> <3c> DW_AT_decl_file : 2
> <3d> DW_AT_decl_line : 37
> <3e> DW_AT_decl_column : 13
> <3f> DW_AT_prototyped : 1
> <3f> DW_AT_type : <0x43>
> <43> DW_AT_declaration : 1
> <1><43>: Abbrev Number: 3 (DW_TAG_pointer_type)
> <44> DW_AT_byte_size : 8
> <45> DW_AT_type : <0x49>
> <1><49>: Abbrev Number: 4 (DW_TAG_base_type)
> <4a> DW_AT_byte_size : 4
> <4b> DW_AT_encoding : 5 (signed)
> <4c> DW_AT_name : int
>
> If you wish to see this for yourself, from your gdb build directory,
> do:
>
> make check TESTS=gdb.base/errno.exp
> readelf -w testsuite/outputs/gdb.base/errno/errno-macros | less
>
> With this commit in place, using gcc as the C compiler, 8 XFAILs in
> gdb.base/errno.exp turn into PASSes. They are:
>
> XFAIL: gdb.base/errno.exp: macros: print (int) errno
> XFAIL: gdb.base/errno.exp: macros: print errno
> XFAIL: gdb.base/errno.exp: pthreads-macros: print (int) errno
> XFAIL: gdb.base/errno.exp: pthreads-macros: print errno
> XFAIL: gdb.base/errno.exp: pthreads-static-macros: print (int) errno
> XFAIL: gdb.base/errno.exp: pthreads-static-macros: print errno
> XFAIL: gdb.base/errno.exp: static-macros: print (int) errno
> XFAIL: gdb.base/errno.exp: static-macros: print errno
>
> For the example shown earlier, GDB is now able to print the correct
> value for errno.
>
> As mentioned earlier, it doesn't work for all compliers. In
> particular, when clang is used instead, there's (currently) no change
> in results in the errno.exp test since clang doesn't provide the
> necessary declaration(s) in its DWARF output.
>
> Perhaps even more compelling is being able to call functions like
> malloc() without having debug info for the C library. To demonstrate
> this, I'll use the test program from gdb.base/break.exp. After
> starting the program (and not letting debuginfod fetch GLIBC's
> symbols), an unpatched GDB will show:
>
> (gdb) ptype malloc
> type = <unknown return type> ()
> (gdb) p malloc(4)
> 'malloc' has unknown return type; cast the call to its declared
> return type
>
> However, with this commit, we now see:
>
> (gdb) ptype malloc
> type = void *(unsigned long)
> (gdb) p malloc(4)
> $1 = (void *) 0x4042a0
>
> This commit changes the name of read_func_scope in gdb/dwarf2/read.c
> to read_func_scope_or_decl, changing all callers. I also added a
> comment for this function.
>
> It introduces a new function, die_is_func_decl_p and uses it in
> read_func_scope_or_decl(). If the call to die_is_func_decl_p()
> returns true, the code in read_func_scope_or_decl which attempts to
> get the function bounds is skipped and, after existing code which
> attempts to do some template related stuff happens, a new symbol with
> address class LOC_UNRESOLVED will be added.
>
> If just this change alone is made and regression testing is performed,
> there are quite a few regressions (well over 50, as I recall), mostly
> due to the fact that the PLT symbol / declaration is now found in
> various cases, perhaps ahead of the symbol for the function
> definition. I'll go into depth regarding the various cases, below.
>
> Many of the regressions were fixed by making the LOC_UNRESOLVED case
> in language_defn::read_var_value in gdb/findvar.c prefer "normal"
> symbols over PLT symbols, though the PLT symbol will be used if no
> normal symbol is found.
>
> This change contains a (perhaps) surprising addition to deal with GNU
> ifunc symbols:
>
> if (bmsym.minsym->type () == mst_text_gnu_ifunc)
> {
> /* GNU ifunc code elsewhere in GDB depends
> on the symbol's type being set as shown
> below. But, coming into this function,
> VAR might have an arguably better type
> obtained from a declaration, i.e.
> DW_AT_declaration. In this case, the
> PLT (solib trampoline) symbol is
> usually found first; see above.
> Nevertheless, we change the type to
> what the rest of GDB expects in order
> for the rest of the GNU ifunc related
> code in GDB to work. */
> type = builtin_type (objfile)
> ->nodebug_text_gnu_ifunc_symbol;
> }
>
> Hopefully, the comment adequately describes what this is about, but
> I'll note that without this particular bit of code, we see the
> following GNU ifunc related failures:
>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: gdb-command<p (int) gnu_ifunc (3)>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: gdb-command<p gnu_ifunc (3)>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: p gnu_ifunc executing
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: p gnu_ifunc()
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: resolver received HWCAP
>
> There are 17 more, but they're essentially repeats of the above, with
> varying resolver_attr, resolver_debug, and final_debug cases.
>
> The change to info_address_command in gdb/printcmd.c forces execution
> into the minimal symbol lookup case when presented with a
> LOC_UNRESOLVED function symbol. Without this change, there were 12
> falures in gdb.base/gnu-ifunc.exp, two of which look like this:
>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: info addr gnu_ifunc
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: info sym <gnu_ifunc-address>
>
> The remaining failures are similar, only differing in the values
> for resolver_attr, resolver_debug, and final_debug.
>
> With regard to the failure itself, for the first one, the log output
> looks like this:
>
> info addr gnu_ifunc
> Symbol "gnu_ifunc" is static storage at address 0x7ffff7fbb389.
> (gdb) FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: info addr gnu_ifunc
>
> The expected message from "info addr gnu_ifunc" was:
>
> Symbol "gnu_ifunc" is at 0x7ffff7fbb389 in a file compiled without
> debugging.
>
> I don't think that the FAILing message is wrong, but I think that the
> PASSing message (regarding being in a file without debugging) is more
> helpful to the user.
>
> It bothered me that the only tests which caught this problem were
> in gdb.base/gnu-ifunc.exp. There is now an "info addr foo" test
> in the new test case gdb.dwarf2/func-decl.exp which also performs
> this test.
>
> With the above change in place, we then see these failures:
>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=1:
> final_debug=0: info addr gnu_ifunc
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=1:
> final_debug=1: info addr gnu_ifunc
>
> (There are two others for "info sym <gnu_ifunc-address>".)
>
> In each case, we now see a message like this...
>
> Symbol "gnu_ifunc" is at 0x7ffff7fbb389 in a file compiled without
> debugging.
>
> ...when we should in fact see:
>
> Symbol "gnu_ifunc" is a function at address 0x7ffff7fbb389.
>
> Note that this is for the resolver_debug=1 case; for this case, the
> resolver library has symbols, so the latter message makes sense and
> the "failing" message is just plain wrong.
>
> These new failures are fixed by the change to
> lookup_global_or_static_symbol in gdb/symtab.c. In this change,
> normal function symbols are preferred to those whose address class is
> LOC_UNRESOLVED. I used a similar approach to that for
> language_defn::read_var_value, discussed earlier.
>
> Again, it seemed to me that there should be a non-gnu-ifunc test
> for this, so I added one; it'll be tested by:
>
> gdb.dwarf2/func-decl.exp: lib_debug: info addr foo
>
> There were also regressions in gdb.base/info-fun.exp and
> gdb.mi/mi-sym-info.exp:
>
> FAIL: gdb.base/info-fun.exp: n_flag=0: IN: info fun foo
> FAIL: gdb.base/info-fun.exp: n_flag=0: NO: info fun foo
> FAIL: gdb.base/info-fun.exp: n_flag=0: SEP: info fun foo
> FAIL: gdb.base/info-fun.exp: n_flag=1: IN: info fun -n foo
> FAIL: gdb.base/info-fun.exp: n_flag=1: NO: info fun -n foo
> FAIL: gdb.base/info-fun.exp: n_flag=1: SEP: info fun -n foo
> FAIL: gdb.mi/mi-sym-info.exp: List all functions matching pattern f3
> (unexpected output)
>
> For each of these failures, there was more output than expected. For
> example, for one of the failing cases...
>
> (gdb) info fun foo
> All functions matching regular expression "foo":
>
> File .../gdb/testsuite/gdb.base/info-fun.c:
> 16: int foo(void);
>
> Non-debugging symbols:
> 0x0000000000400370 foo@plt
> 0x00007ffff7fbb389 foo
> (gdb) FAIL: gdb.base/info-fun.exp: n_flag=0: NO: info fun foo
>
> The "passing" output looks like this:
>
> (gdb) info fun foo
> All functions matching regular expression "foo":
>
> Non-debugging symbols:
> 0x0000000000400370 foo@plt
> 0x00007ffff7fbb389 foo
> (gdb) PASS: gdb.base/info-fun.exp: n_flag=0: NO: info fun foo
>
> At first glance, the "failing" output looks useful; perhaps it could
> be, but I'll note that the extra lines being output are for a
> declaration for a function which is not in the CU where the function
> is defined. I have a hunch that we might be overwhelmed by extra
> output in a program with many libraries - it's conceivable that for
> some symbols, each library would have its own declaration.
>
> In any case, I was able to obtain the original / passing behavior
> by discarding LOC_UNRESOLVED symbols when searching in the
> function domain in global_symbol_searcher::add_matching_symbols.
>
> Finally, there were two regressions in gdb.base/completion.exp:
>
> FAIL: gdb.base/completion.exp: complete break break.c:ma
> FAIL: gdb.base/completion.exp: tab complete break break.c:ma (timeout)
>
> The log file for these failing tests is not especially helpful, but I
> debugged it by throwing a "gdb_interact" into the test to see what
> was going on. As I recall, when trying to complete "break.c:ma",
> "marker1", "marker2", "marker3", "marker4", and "malloc" were all
> being found in addition to "main", which is what the what the testcase
> was expecting to be the sole completion.
>
> This problem was fixed by adjusting completion_skip_symbol in
> symtab.h.
>
> The new test case, gdb.dwarf2/func-decl.exp, contains, in addition to
> the tests already discussed, two tests which will fail in a GDB built
> without this commit and pass in a GDB built with it...
>
> PASS: gdb.dwarf2/func-decl.exp: no_lib_debug:
> gdb-command<print foo ("abc", 5)>
> PASS: gdb.dwarf2/func-decl.exp: no_lib_debug: ptype foo
>
> The remaining tests in gdb.dwarf2/func-decl.exp should all pass in a
> GDB built with or without this commit. They will only fail if one of
> the relevant changes discussed above is missing or becomes broken for
> some reason (perhaps due to some future change to this area of the
> code).
>
> Regarding the use of the DWARF assembler in the test... Using some
> version(s) of GNU C, it's possible to write a test which causes a
> suitable declaration DIE to be placed in the DWARF output. In fact, I
> originally wrote most of the new test without the DWARF assembler.
> But not all compilers do this, e.g. clang does not, and I wanted a
> test which would test this functionality regardless of whether the
> compiler generates the DWARF required for this test.
>
> I've tested on Fedora 42 w/ architectures x86_64, aarch64, riscv,
> s390x, and ppc64le. On x86_64 Fedora 42, I've also tested with
> --target_board=unix/-m32, --target_board=native-gdbserver, and
> --target_board=native-extended-gdbserver. No regressions found.
>
> After skimming version 1 of this commit, Tom Tromey suggested that
> there should also be changes to the indexer. This version 2 commit
> adds that by making DW_TAG_subprogram declarations "interesting" to
> the indexer. The changes which do this are in gdb/dwarf2/abbrev.c
> and gdb/dwarf2/cooked-indexer.c. I also added a test to the new
> test case which attempts to do "ptype foo" prior to starting the
> program. This failed when using version 1 of this commit, but
> passes now.
Thanks for the detailed explanation. I took a first pass through the
changes and just has a couple of really minor thoughts. I'll take a
deeper look next week.
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31563
> ---
> gdb/dwarf2/abbrev.c | 3 +
> gdb/dwarf2/cooked-indexer.c | 5 +
> gdb/dwarf2/read.c | 108 ++++++++++----
> gdb/findvar.c | 46 +++++-
> gdb/printcmd.c | 4 +-
> gdb/symtab.c | 43 ++++--
> gdb/symtab.h | 20 ++-
> gdb/testsuite/gdb.dwarf2/func-decl-lib.c | 24 +++
> gdb/testsuite/gdb.dwarf2/func-decl.c | 35 +++++
> gdb/testsuite/gdb.dwarf2/func-decl.exp | 182 +++++++++++++++++++++++
> 10 files changed, 427 insertions(+), 43 deletions(-)
> create mode 100644 gdb/testsuite/gdb.dwarf2/func-decl-lib.c
> create mode 100644 gdb/testsuite/gdb.dwarf2/func-decl.c
> create mode 100644 gdb/testsuite/gdb.dwarf2/func-decl.exp
>
> diff --git a/gdb/dwarf2/abbrev.c b/gdb/dwarf2/abbrev.c
> index 5cfff69cc3b..2ca80ab8a6a 100644
> --- a/gdb/dwarf2/abbrev.c
> +++ b/gdb/dwarf2/abbrev.c
> @@ -240,6 +240,9 @@ abbrev_table::read (struct dwarf2_section_info *section,
> the correct scope. */
> cur_abbrev->interesting = true;
> }
> + else if (has_hardcoded_declaration
> + && cur_abbrev->tag == DW_TAG_subprogram)
> + cur_abbrev->interesting = true;
> else if (has_hardcoded_declaration
> && (cur_abbrev->tag != DW_TAG_variable || !has_external))
> cur_abbrev->interesting = false;
> diff --git a/gdb/dwarf2/cooked-indexer.c b/gdb/dwarf2/cooked-indexer.c
> index c093984bae0..710ef82ed0d 100644
> --- a/gdb/dwarf2/cooked-indexer.c
> +++ b/gdb/dwarf2/cooked-indexer.c
> @@ -301,6 +301,11 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu,
> || abbrev->tag == DW_TAG_namespace)
> && abbrev->has_children)
> *flags |= IS_TYPE_DECLARATION;
> + else if (abbrev->tag == DW_TAG_subprogram)
> + {
> + /* We want to index function declarations - do nothing in order
> + to avoid nulling out *name, below. */
> + }
I wonder if the comment a few lines up, that looks like:
/* We don't want to examine declarations, but if we found a
declaration when handling DW_AT_specification or the like, then
that is ok. Similarly, we allow an external variable without a
location; those are resolved via minimal symbols. */
is now out of date after this change. I don't really understand the
DW_AT_specification part though so I cannot really suggest
improvements...
> else
> {
> *linkage_name = nullptr;
> diff --git a/gdb/testsuite/gdb.dwarf2/func-decl.exp b/gdb/testsuite/gdb.dwarf2/func-decl.exp
> new file mode 100644
> index 00000000000..316dfe8d04c
> --- /dev/null
> +++ b/gdb/testsuite/gdb.dwarf2/func-decl.exp
> @@ -0,0 +1,182 @@
> +# Copyright 2025 Free Software Foundation, Inc.
> +# 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.
> +
> +# Test GDB's ability to access declarations for function symbols. On
> +# Linux, using GCC, compiling the main program with -g and the shared
> +# lib source file without -g can be used to create a compelling test
> +# case without needing the DWARF assembler. However, we don't want to
> +# count on the fact that the compiler will place a declaration for the
> +# shared library function in the DWARF info for the main program. (E.g.
> +# when using CLANG/LLVM, these DIEs are omitted.) Therefore, we use the
> +# DWARF assembler to create the requisite DWARF info for this test.
> +
> +load_lib dwarf.exp
> +require dwarf2_support
> +
> +standard_testfile .c -dw.S
> +
> +set asm_file [standard_output_file $srcfile2]
> +set libsrc "${srcdir}/${subdir}/${testfile}-lib.c"
> +set libobj [standard_output_file "${testfile}-lib.so"]
> +
> +# We need to know the size of integer and address types in order to
> +# write some of the debugging info we'd like to generate.
> +#
> +# For that, we ask GDB by debugging our test program. Any program
> +# would do, but since we already have program written specifically for
> +# this testcase, we might as well use that. Note that we need to
> +# also build the shared library that the test program uses.
> +
> +set session_options [list debug shlib=${libobj}]
> +if { [gdb_compile_shlib $libsrc $libobj [list nodebug]] != "" } {
> + untested "failed to compile shared object"
> + return -1
> +}
> +
> +if { [prepare_for_testing "failed to prepare" ${binfile} \
> + [list $srcfile] $session_options] } {
> + return -1
> +}
> +
> +with_test_prefix "first session" {
> + if ![runto_main] {
> + return
> + }
> +
> + with_shared_gdb {
> + # Rather than start a new session, declare the current session the
> + # shared one. Otherwise, get_func_info would compile an executable
> + # in a temp dir; due to implementation details, this means that the
> + # shared lib won't be found.
> + share_gdb ${srcdir}/${subdir}/$srcfile $session_options
> +
> + get_func_info main $session_options
> +
> + # Using the running GDB session, determine sizes of several types.
> + set int_size [get_sizeof "int" -1]
> + set char_ptr_size [get_sizeof "char *" 8]
> + }
> +}
> +
> +Dwarf::assemble $asm_file {
> + cu {} {
> + DW_TAG_compile_unit {
> + {DW_AT_language @DW_LANG_C99}
> + {DW_AT_name $::srcfile}
> + } {
> + declare_labels int_label char_label char_ptr_label
> +
> + int_label: DW_TAG_base_type {
> + {DW_AT_byte_size ${::int_size} DW_FORM_udata}
> + {DW_AT_encoding @DW_ATE_signed}
> + {DW_AT_name "int"}
> + }
> +
> + char_label: DW_TAG_base_type {
> + {byte_size 1 sdata}
> + {encoding @DW_ATE_signed_char}
> + {name "char"}
> + }
> +
> + char_ptr_label: DW_TAG_pointer_type {
> + {DW_AT_byte_size ${::char_ptr_size} DW_FORM_sdata}
> + {DW_AT_type :$char_label}
> + }
> +
> + DW_TAG_subprogram {
> + {DW_AT_external 1 flag}
> + {DW_AT_name foo}
> + {DW_AT_prototyped 1 DW_FORM_flag_present}
> + {DW_AT_type :$int_label}
> + {DW_AT_declaration 1 flag}
> + } {
> + DW_TAG_formal_parameter {
> + {DW_AT_type :$char_ptr_label}
> + }
> + DW_TAG_formal_parameter {
> + {DW_AT_type :$int_label}
> + }
> + }
> +
> + DW_TAG_subprogram {
> + {DW_AT_name main}
> + {DW_AT_low_pc ${::main_start} DW_FORM_addr}
> + {DW_AT_high_pc ${::main_end} DW_FORM_addr}
> + {DW_AT_type :$int_label}
> + }
> + }
> + }
> +}
> +
> +# Test against a shared library built with no debugging symbols. Due
> +# to the DWARF info provided by the DWARF assembler above, there will
> +# be a declaration for the shared lib symbol "foo" in the main
> +# program. Thus, due to the lack of DWARF info in the shared library,
> +# GDB can't know the type from the shared library. Instead, it must
> +# rely on the declaration of foo from the main program.
> +#
> +# Due to that declaration, it should be possible to examine its type
> +# as well as make an inferior function call. We expect "info addr foo"
> +# to provide the address of the actual function instead of foo's PLT
> +# in the main program.
> +
> +with_test_prefix no_lib_debug {
> + if { [gdb_compile_shlib $libsrc $libobj [list nodebug]] != "" } {
> + untested "failed to compile shared object"
> + return -1
> + }
> +
> + if { [prepare_for_testing "failed to prepare" ${binfile} \
> + [list $srcfile $asm_file] [list nodebug shlib=${libobj}]] } {
> + return -1
> + }
> +
> + with_test_prefix "before program start" {
> + # Verify that the type of foo is available prior to starting
> + # the program.
> + gdb_test "ptype foo" "^type = int \\(char \\*, int\\)"
> + }
> +
> + clean_restart $binfile
Is this clean_restart really needed? I suspect not.
Thanks,
Andrew
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v2] gdb/dwarf2: Add symbols for function declarations
2025-07-03 19:45 [PATCH v2] gdb/dwarf2: Add symbols for function declarations Kevin Buettner
` (2 preceding siblings ...)
2025-08-29 18:30 ` Andrew Burgess
@ 2025-09-05 15:24 ` Andrew Burgess
2025-09-05 15:38 ` Tom Tromey
4 siblings, 0 replies; 9+ messages in thread
From: Andrew Burgess @ 2025-09-05 15:24 UTC (permalink / raw)
To: Kevin Buettner, gdb-patches; +Cc: Kevin Buettner
Kevin Buettner <kevinb@redhat.com> writes:
> This commit was motivated by comments 3 and 4 for bug 31563:
>
> https://sourceware.org/bugzilla/show_bug.cgi?id=31563#c3
>
> When a program is built with -g3, macro information is available to
> GDB; for errno, the macro defined in /usr/include/errno.h (provided by
> GLIBC) looks like this:
>
> # define errno (*__errno_location ())
>
> However, up to now, GDB doesn't know the type of __errno_location,
> despite (sometimes) having a DIE representing a declaration providing
> its type. In any case, apparently not knowing the return type of
> __errno_location, GDB was unable to perform the inferior function call
> specified by the errno macro:
>
> (gdb) p errno
> '__errno_location' has unknown return type; cast the call to its
> declared return type
>
> But, for some compilers, GDB *should* be able to know the type. These
> are the DIEs related to the __errno_location declaration from the
> "macros" case for the gdb.base/errno.exp test:
>
> <1><37>: Abbrev Number: 2 (DW_TAG_subprogram)
> <38> DW_AT_external : 1
> <38> DW_AT_name : (indirect string, offset: 0x20e4):
> __errno_location
> <3c> DW_AT_decl_file : 2
> <3d> DW_AT_decl_line : 37
> <3e> DW_AT_decl_column : 13
> <3f> DW_AT_prototyped : 1
> <3f> DW_AT_type : <0x43>
> <43> DW_AT_declaration : 1
> <1><43>: Abbrev Number: 3 (DW_TAG_pointer_type)
> <44> DW_AT_byte_size : 8
> <45> DW_AT_type : <0x49>
> <1><49>: Abbrev Number: 4 (DW_TAG_base_type)
> <4a> DW_AT_byte_size : 4
> <4b> DW_AT_encoding : 5 (signed)
> <4c> DW_AT_name : int
>
> If you wish to see this for yourself, from your gdb build directory,
> do:
>
> make check TESTS=gdb.base/errno.exp
> readelf -w testsuite/outputs/gdb.base/errno/errno-macros | less
>
> With this commit in place, using gcc as the C compiler, 8 XFAILs in
> gdb.base/errno.exp turn into PASSes. They are:
>
> XFAIL: gdb.base/errno.exp: macros: print (int) errno
> XFAIL: gdb.base/errno.exp: macros: print errno
> XFAIL: gdb.base/errno.exp: pthreads-macros: print (int) errno
> XFAIL: gdb.base/errno.exp: pthreads-macros: print errno
> XFAIL: gdb.base/errno.exp: pthreads-static-macros: print (int) errno
> XFAIL: gdb.base/errno.exp: pthreads-static-macros: print errno
> XFAIL: gdb.base/errno.exp: static-macros: print (int) errno
> XFAIL: gdb.base/errno.exp: static-macros: print errno
>
> For the example shown earlier, GDB is now able to print the correct
> value for errno.
>
> As mentioned earlier, it doesn't work for all compliers. In
> particular, when clang is used instead, there's (currently) no change
> in results in the errno.exp test since clang doesn't provide the
> necessary declaration(s) in its DWARF output.
>
> Perhaps even more compelling is being able to call functions like
> malloc() without having debug info for the C library. To demonstrate
> this, I'll use the test program from gdb.base/break.exp. After
> starting the program (and not letting debuginfod fetch GLIBC's
> symbols), an unpatched GDB will show:
>
> (gdb) ptype malloc
> type = <unknown return type> ()
> (gdb) p malloc(4)
> 'malloc' has unknown return type; cast the call to its declared
> return type
>
> However, with this commit, we now see:
>
> (gdb) ptype malloc
> type = void *(unsigned long)
> (gdb) p malloc(4)
> $1 = (void *) 0x4042a0
>
> This commit changes the name of read_func_scope in gdb/dwarf2/read.c
> to read_func_scope_or_decl, changing all callers. I also added a
> comment for this function.
>
> It introduces a new function, die_is_func_decl_p and uses it in
> read_func_scope_or_decl(). If the call to die_is_func_decl_p()
> returns true, the code in read_func_scope_or_decl which attempts to
> get the function bounds is skipped and, after existing code which
> attempts to do some template related stuff happens, a new symbol with
> address class LOC_UNRESOLVED will be added.
>
> If just this change alone is made and regression testing is performed,
> there are quite a few regressions (well over 50, as I recall), mostly
> due to the fact that the PLT symbol / declaration is now found in
> various cases, perhaps ahead of the symbol for the function
> definition. I'll go into depth regarding the various cases, below.
>
> Many of the regressions were fixed by making the LOC_UNRESOLVED case
> in language_defn::read_var_value in gdb/findvar.c prefer "normal"
> symbols over PLT symbols, though the PLT symbol will be used if no
> normal symbol is found.
>
> This change contains a (perhaps) surprising addition to deal with GNU
> ifunc symbols:
>
> if (bmsym.minsym->type () == mst_text_gnu_ifunc)
> {
> /* GNU ifunc code elsewhere in GDB depends
> on the symbol's type being set as shown
> below. But, coming into this function,
> VAR might have an arguably better type
> obtained from a declaration, i.e.
> DW_AT_declaration. In this case, the
> PLT (solib trampoline) symbol is
> usually found first; see above.
> Nevertheless, we change the type to
> what the rest of GDB expects in order
> for the rest of the GNU ifunc related
> code in GDB to work. */
> type = builtin_type (objfile)
> ->nodebug_text_gnu_ifunc_symbol;
> }
>
> Hopefully, the comment adequately describes what this is about, but
> I'll note that without this particular bit of code, we see the
> following GNU ifunc related failures:
>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: gdb-command<p (int) gnu_ifunc (3)>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: gdb-command<p gnu_ifunc (3)>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: p gnu_ifunc executing
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: p gnu_ifunc()
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: resolver received HWCAP
>
> There are 17 more, but they're essentially repeats of the above, with
> varying resolver_attr, resolver_debug, and final_debug cases.
>
> The change to info_address_command in gdb/printcmd.c forces execution
> into the minimal symbol lookup case when presented with a
> LOC_UNRESOLVED function symbol. Without this change, there were 12
> falures in gdb.base/gnu-ifunc.exp, two of which look like this:
>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: info addr gnu_ifunc
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: info sym <gnu_ifunc-address>
>
> The remaining failures are similar, only differing in the values
> for resolver_attr, resolver_debug, and final_debug.
>
> With regard to the failure itself, for the first one, the log output
> looks like this:
>
> info addr gnu_ifunc
> Symbol "gnu_ifunc" is static storage at address 0x7ffff7fbb389.
> (gdb) FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=0:
> final_debug=0: info addr gnu_ifunc
>
> The expected message from "info addr gnu_ifunc" was:
>
> Symbol "gnu_ifunc" is at 0x7ffff7fbb389 in a file compiled without
> debugging.
>
> I don't think that the FAILing message is wrong, but I think that the
> PASSing message (regarding being in a file without debugging) is more
> helpful to the user.
>
> It bothered me that the only tests which caught this problem were
> in gdb.base/gnu-ifunc.exp. There is now an "info addr foo" test
> in the new test case gdb.dwarf2/func-decl.exp which also performs
> this test.
>
> With the above change in place, we then see these failures:
>
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=1:
> final_debug=0: info addr gnu_ifunc
> FAIL: gdb.base/gnu-ifunc.exp: resolver_attr=0: resolver_debug=1:
> final_debug=1: info addr gnu_ifunc
>
> (There are two others for "info sym <gnu_ifunc-address>".)
>
> In each case, we now see a message like this...
>
> Symbol "gnu_ifunc" is at 0x7ffff7fbb389 in a file compiled without
> debugging.
>
> ...when we should in fact see:
>
> Symbol "gnu_ifunc" is a function at address 0x7ffff7fbb389.
>
> Note that this is for the resolver_debug=1 case; for this case, the
> resolver library has symbols, so the latter message makes sense and
> the "failing" message is just plain wrong.
>
> These new failures are fixed by the change to
> lookup_global_or_static_symbol in gdb/symtab.c. In this change,
> normal function symbols are preferred to those whose address class is
> LOC_UNRESOLVED. I used a similar approach to that for
> language_defn::read_var_value, discussed earlier.
>
> Again, it seemed to me that there should be a non-gnu-ifunc test
> for this, so I added one; it'll be tested by:
>
> gdb.dwarf2/func-decl.exp: lib_debug: info addr foo
>
> There were also regressions in gdb.base/info-fun.exp and
> gdb.mi/mi-sym-info.exp:
>
> FAIL: gdb.base/info-fun.exp: n_flag=0: IN: info fun foo
> FAIL: gdb.base/info-fun.exp: n_flag=0: NO: info fun foo
> FAIL: gdb.base/info-fun.exp: n_flag=0: SEP: info fun foo
> FAIL: gdb.base/info-fun.exp: n_flag=1: IN: info fun -n foo
> FAIL: gdb.base/info-fun.exp: n_flag=1: NO: info fun -n foo
> FAIL: gdb.base/info-fun.exp: n_flag=1: SEP: info fun -n foo
> FAIL: gdb.mi/mi-sym-info.exp: List all functions matching pattern f3
> (unexpected output)
>
> For each of these failures, there was more output than expected. For
> example, for one of the failing cases...
>
> (gdb) info fun foo
> All functions matching regular expression "foo":
>
> File .../gdb/testsuite/gdb.base/info-fun.c:
> 16: int foo(void);
>
> Non-debugging symbols:
> 0x0000000000400370 foo@plt
> 0x00007ffff7fbb389 foo
> (gdb) FAIL: gdb.base/info-fun.exp: n_flag=0: NO: info fun foo
>
> The "passing" output looks like this:
>
> (gdb) info fun foo
> All functions matching regular expression "foo":
>
> Non-debugging symbols:
> 0x0000000000400370 foo@plt
> 0x00007ffff7fbb389 foo
> (gdb) PASS: gdb.base/info-fun.exp: n_flag=0: NO: info fun foo
>
> At first glance, the "failing" output looks useful; perhaps it could
> be, but I'll note that the extra lines being output are for a
> declaration for a function which is not in the CU where the function
> is defined. I have a hunch that we might be overwhelmed by extra
> output in a program with many libraries - it's conceivable that for
> some symbols, each library would have its own declaration.
>
> In any case, I was able to obtain the original / passing behavior
> by discarding LOC_UNRESOLVED symbols when searching in the
> function domain in global_symbol_searcher::add_matching_symbols.
>
> Finally, there were two regressions in gdb.base/completion.exp:
>
> FAIL: gdb.base/completion.exp: complete break break.c:ma
> FAIL: gdb.base/completion.exp: tab complete break break.c:ma (timeout)
>
> The log file for these failing tests is not especially helpful, but I
> debugged it by throwing a "gdb_interact" into the test to see what
> was going on. As I recall, when trying to complete "break.c:ma",
> "marker1", "marker2", "marker3", "marker4", and "malloc" were all
> being found in addition to "main", which is what the what the testcase
> was expecting to be the sole completion.
>
> This problem was fixed by adjusting completion_skip_symbol in
> symtab.h.
>
> The new test case, gdb.dwarf2/func-decl.exp, contains, in addition to
> the tests already discussed, two tests which will fail in a GDB built
> without this commit and pass in a GDB built with it...
>
> PASS: gdb.dwarf2/func-decl.exp: no_lib_debug:
> gdb-command<print foo ("abc", 5)>
> PASS: gdb.dwarf2/func-decl.exp: no_lib_debug: ptype foo
>
> The remaining tests in gdb.dwarf2/func-decl.exp should all pass in a
> GDB built with or without this commit. They will only fail if one of
> the relevant changes discussed above is missing or becomes broken for
> some reason (perhaps due to some future change to this area of the
> code).
>
> Regarding the use of the DWARF assembler in the test... Using some
> version(s) of GNU C, it's possible to write a test which causes a
> suitable declaration DIE to be placed in the DWARF output. In fact, I
> originally wrote most of the new test without the DWARF assembler.
> But not all compilers do this, e.g. clang does not, and I wanted a
> test which would test this functionality regardless of whether the
> compiler generates the DWARF required for this test.
>
> I've tested on Fedora 42 w/ architectures x86_64, aarch64, riscv,
> s390x, and ppc64le. On x86_64 Fedora 42, I've also tested with
> --target_board=unix/-m32, --target_board=native-gdbserver, and
> --target_board=native-extended-gdbserver. No regressions found.
>
> After skimming version 1 of this commit, Tom Tromey suggested that
> there should also be changes to the indexer. This version 2 commit
> adds that by making DW_TAG_subprogram declarations "interesting" to
> the indexer. The changes which do this are in gdb/dwarf2/abbrev.c
> and gdb/dwarf2/cooked-indexer.c. I also added a test to the new
> test case which attempts to do "ptype foo" prior to starting the
> program. This failed when using version 1 of this commit, but
> passes now.
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31563
I'm not really super familiar with all of the symbol management logic,
ideally Tom would take a look through this. But for what it's worth,
after having a look through, this all made sense to me.
It's a shame about the ifunc related block in findvar.c, I did play for
a while to see if I could figure out a way to avoid that code, but I
couldn't come up with anything better.
What we'd need I believe, is for the declaration's type to be marked by
a call to 'type->set_is_gnu_ifunc (true);'. If, as a total hack, we
mark all declaration types that way in dwarf2/read.c when they are
created, then the findvar.c ifunc block is no longer needed. The
problem, of course, is figuring out in dwarf2/read.c which types should
be marked, and which shouldn't, and I don't think you know the answer to
that until you've looked at the objfile that contains the function
implementation...
As I said, I don't consider myself expert enough to give an approval
tag, so this will have to do:
Reviewed-By: Andrew Burgess <aburgess@redhat.com>
Thanks,
Andrew
> ---
> gdb/dwarf2/abbrev.c | 3 +
> gdb/dwarf2/cooked-indexer.c | 5 +
> gdb/dwarf2/read.c | 108 ++++++++++----
> gdb/findvar.c | 46 +++++-
> gdb/printcmd.c | 4 +-
> gdb/symtab.c | 43 ++++--
> gdb/symtab.h | 20 ++-
> gdb/testsuite/gdb.dwarf2/func-decl-lib.c | 24 +++
> gdb/testsuite/gdb.dwarf2/func-decl.c | 35 +++++
> gdb/testsuite/gdb.dwarf2/func-decl.exp | 182 +++++++++++++++++++++++
> 10 files changed, 427 insertions(+), 43 deletions(-)
> create mode 100644 gdb/testsuite/gdb.dwarf2/func-decl-lib.c
> create mode 100644 gdb/testsuite/gdb.dwarf2/func-decl.c
> create mode 100644 gdb/testsuite/gdb.dwarf2/func-decl.exp
>
> diff --git a/gdb/dwarf2/abbrev.c b/gdb/dwarf2/abbrev.c
> index 5cfff69cc3b..2ca80ab8a6a 100644
> --- a/gdb/dwarf2/abbrev.c
> +++ b/gdb/dwarf2/abbrev.c
> @@ -240,6 +240,9 @@ abbrev_table::read (struct dwarf2_section_info *section,
> the correct scope. */
> cur_abbrev->interesting = true;
> }
> + else if (has_hardcoded_declaration
> + && cur_abbrev->tag == DW_TAG_subprogram)
> + cur_abbrev->interesting = true;
> else if (has_hardcoded_declaration
> && (cur_abbrev->tag != DW_TAG_variable || !has_external))
> cur_abbrev->interesting = false;
> diff --git a/gdb/dwarf2/cooked-indexer.c b/gdb/dwarf2/cooked-indexer.c
> index c093984bae0..710ef82ed0d 100644
> --- a/gdb/dwarf2/cooked-indexer.c
> +++ b/gdb/dwarf2/cooked-indexer.c
> @@ -301,6 +301,11 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu,
> || abbrev->tag == DW_TAG_namespace)
> && abbrev->has_children)
> *flags |= IS_TYPE_DECLARATION;
> + else if (abbrev->tag == DW_TAG_subprogram)
> + {
> + /* We want to index function declarations - do nothing in order
> + to avoid nulling out *name, below. */
> + }
> else
> {
> *linkage_name = nullptr;
> diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
> index 5e18e452061..ea6f508d96e 100644
> --- a/gdb/dwarf2/read.c
> +++ b/gdb/dwarf2/read.c
> @@ -842,7 +842,7 @@ static void read_file_scope (struct die_info *, struct dwarf2_cu *);
>
> static void read_type_unit_scope (struct die_info *, struct dwarf2_cu *);
>
> -static void read_func_scope (struct die_info *, struct dwarf2_cu *);
> +static void read_func_scope_or_decl (struct die_info *, struct dwarf2_cu *);
>
> static void read_lexical_block_scope (struct die_info *, struct dwarf2_cu *);
>
> @@ -5012,7 +5012,7 @@ process_die (struct die_info *die, struct dwarf2_cu *cu)
> /* Fall through. */
> case DW_TAG_entry_point:
> case DW_TAG_inlined_subroutine:
> - read_func_scope (die, cu);
> + read_func_scope_or_decl (die, cu);
> break;
> case DW_TAG_lexical_block:
> case DW_TAG_try_block:
> @@ -8366,8 +8366,42 @@ fixup_low_high_pc (struct dwarf2_cu *cu, struct die_info *die, CORE_ADDR *low_pc
> }
> }
>
> +/* Return true if DIE represents a prototyped function declaration
> + with a return type and the function named NAME has a minimal
> + symbol in the CU's objfile. (NAME has already been extracted
> + from the DIE.) Return false otherwise. */
> +
> +static bool
> +die_is_func_decl_p (struct die_info *die, struct dwarf2_cu *cu,
> + const char * name)
> +{
> + if (die->tag != DW_TAG_subprogram)
> + return false;
> +
> + attribute *attr = dwarf2_attr (die, DW_AT_declaration, cu);
> + if (attr == nullptr || !attr->as_boolean ())
> + return false;
> +
> + attr = dwarf2_attr (die, DW_AT_type, cu);
> + if (attr == nullptr)
> + return false;
> +
> + attr = dwarf2_attr (die, DW_AT_prototyped, cu);
> + if (attr == nullptr || !attr->as_boolean ())
> + return false;
> +
> + bound_minimal_symbol mfunsym
> + = lookup_minimal_symbol (current_program_space, name,
> + cu->per_objfile->objfile);
> +
> + return (mfunsym.minsym != nullptr);
> +}
> +
> +/* Record symbol and related info for a function defined in
> + CU or for certain declarations (defined elsewhere). */
> +
> static void
> -read_func_scope (struct die_info *die, struct dwarf2_cu *cu)
> +read_func_scope_or_decl (struct die_info *die, struct dwarf2_cu *cu)
> {
> dwarf2_per_objfile *per_objfile = cu->per_objfile;
> struct objfile *objfile = per_objfile->objfile;
> @@ -8417,37 +8451,44 @@ read_func_scope (struct die_info *die, struct dwarf2_cu *cu)
> return;
> }
>
> - /* Ignore functions with missing or invalid low and high pc attributes. */
> - unrelocated_addr unrel_low, unrel_high;
> - if (dwarf2_get_pc_bounds (die, &unrel_low, &unrel_high, cu, nullptr, nullptr)
> - <= PC_BOUNDS_INVALID)
> + bool is_decl = die_is_func_decl_p (die, cu, name);
> +
> + /* If not a declaration, try to find the function bounds. */
> + if (!is_decl)
> {
> - if (have_complaint ())
> + /* Ignore functions with missing or invalid low and high pc
> + attributes. */
> + unrelocated_addr unrel_low, unrel_high;
> + if ((dwarf2_get_pc_bounds (die, &unrel_low, &unrel_high, cu,
> + nullptr, nullptr) <= PC_BOUNDS_INVALID))
> {
> - attr = dwarf2_attr (die, DW_AT_external, cu);
> - bool external_p = attr != nullptr && attr->as_boolean ();
> - attr = dwarf2_attr (die, DW_AT_inline, cu);
> - bool inlined_p = false;
> - if (attr != nullptr)
> + if (have_complaint ())
> {
> - std::optional<ULONGEST> value = attr->unsigned_constant ();
> - inlined_p = (value.has_value ()
> - && (*value == DW_INL_inlined
> - || *value == DW_INL_declared_inlined));
> + attr = dwarf2_attr (die, DW_AT_external, cu);
> + bool external_p = attr != nullptr && attr->as_boolean ();
> + attr = dwarf2_attr (die, DW_AT_inline, cu);
> + bool inlined_p = false;
> + if (attr != nullptr)
> + {
> + std::optional<ULONGEST> value = attr->unsigned_constant ();
> + inlined_p = (value.has_value ()
> + && (*value == DW_INL_inlined
> + || *value == DW_INL_declared_inlined));
> + }
> + attr = dwarf2_attr (die, DW_AT_declaration, cu);
> + bool decl_p = attr != nullptr && attr->as_boolean ();
> + if (!external_p && !inlined_p && !decl_p)
> + complaint (_("cannot get low and high bounds "
> + "for subprogram DIE at %s"),
> + sect_offset_str (die->sect_off));
> }
> - attr = dwarf2_attr (die, DW_AT_declaration, cu);
> - bool decl_p = attr != nullptr && attr->as_boolean ();
> - if (!external_p && !inlined_p && !decl_p)
> - complaint (_("cannot get low and high bounds "
> - "for subprogram DIE at %s"),
> - sect_offset_str (die->sect_off));
> + return;
> }
> - return;
> - }
>
> - lowpc = per_objfile->relocate (unrel_low);
> - highpc = per_objfile->relocate (unrel_high);
> - fixup_low_high_pc (cu, die, &lowpc, &highpc);
> + lowpc = per_objfile->relocate (unrel_low);
> + highpc = per_objfile->relocate (unrel_high);
> + fixup_low_high_pc (cu, die, &lowpc, &highpc);
> + }
>
> /* If we have any template arguments, then we must allocate a
> different sort of symbol. */
> @@ -8462,6 +8503,15 @@ read_func_scope (struct die_info *die, struct dwarf2_cu *cu)
> }
> }
>
> + /* If it's a declaration, record the symbol and return. */
> + if (is_decl)
> + {
> + struct symbol *sym =
> + new_symbol (die, read_type_die (die, cu), cu, templ_func);
> + sym->set_aclass_index (LOC_UNRESOLVED);
> + return;
> + }
> +
> gdb_assert (cu->get_builder () != nullptr);
> newobj = cu->get_builder ()->push_context (0, lowpc);
> newobj->name = new_symbol (die, read_type_die (die, cu), cu, templ_func);
> @@ -11259,7 +11309,7 @@ handle_struct_member_die (struct die_info *child_die, struct type *type,
> However, it does emit ordinary functions as children
> of a struct DIE. */
> if (cu->lang () == language_rust)
> - read_func_scope (child_die, cu);
> + read_func_scope_or_decl (child_die, cu);
> else
> {
> /* C++ member function. */
> diff --git a/gdb/findvar.c b/gdb/findvar.c
> index 9da5c4831a6..4934cf4c2a4 100644
> --- a/gdb/findvar.c
> +++ b/gdb/findvar.c
> @@ -444,19 +444,59 @@ language_defn::read_var_value (struct symbol *var,
> {
> struct obj_section *obj_section;
> bound_minimal_symbol bmsym;
> + bound_minimal_symbol bmsym_solib_tramp;
>
> gdbarch_iterate_over_objfiles_in_search_order
> (var->arch (),
> - [var, &bmsym] (objfile *objfile)
> + [var, &bmsym, &bmsym_solib_tramp, &type] (objfile *objfile)
> {
> bmsym = lookup_minimal_symbol (current_program_space,
> var->linkage_name (), objfile);
>
> - /* Stop if a match is found. */
> - return bmsym.minsym != nullptr;
> + if (bmsym.minsym != nullptr)
> + {
> + if (bmsym.minsym->type () == mst_solib_trampoline)
> + {
> + /* Stash the trampoline symbol in case no
> + better symbol is found. */
> + if (bmsym_solib_tramp.minsym == nullptr)
> + bmsym_solib_tramp = bmsym;
> + /* Keep searching... */
> + bmsym = {};
> + return false;
> + }
> + else
> + {
> + if (bmsym.minsym->type () == mst_text_gnu_ifunc)
> + {
> + /* GNU ifunc code elsewhere in GDB depends
> + on the symbol's type being set as shown
> + below. But, coming into this function,
> + VAR might have an arguably better type
> + obtained from a declaration, i.e.
> + DW_AT_declaration. In this case, the
> + PLT (solib trampoline) symbol is
> + usually found first; see above.
> + Nevertheless, we change the type to
> + what the rest of GDB expects in order
> + for the rest of the GNU ifunc related
> + code in GDB to work. */
> + type = builtin_type (objfile)
> + ->nodebug_text_gnu_ifunc_symbol;
> + }
> + return true;
> + }
> + }
> + else
> + return false;
> },
> var->objfile ());
>
> + /* Use the solib trampoline symbol if an alternative (non trampline)
> + symbol wasn't found. */
> + if (bmsym.minsym == nullptr)
> + bmsym = bmsym_solib_tramp;
> +
> /* If we can't find the minsym there's a problem in the symbol info.
> The symbol exists in the debug info, but it's missing in the minsym
> table. */
> diff --git a/gdb/printcmd.c b/gdb/printcmd.c
> index 19fbc20074e..30be62587bd 100644
> --- a/gdb/printcmd.c
> +++ b/gdb/printcmd.c
> @@ -1585,7 +1585,9 @@ info_address_command (const char *exp, int from_tty)
>
> sym = lookup_symbol (exp, get_selected_block (&context_pc), SEARCH_VFT,
> &is_a_field_of_this).symbol;
> - if (sym == NULL)
> + if (sym == NULL
> + || (sym->aclass () == LOC_UNRESOLVED
> + && symbol_is_function_or_method (sym)))
> {
> if (is_a_field_of_this.type != NULL)
> {
> diff --git a/gdb/symtab.c b/gdb/symtab.c
> index 7d1a0b066c7..e71837cb16e 100644
> --- a/gdb/symtab.c
> +++ b/gdb/symtab.c
> @@ -2654,15 +2654,33 @@ lookup_global_or_static_symbol (const char *name,
>
> /* Do a global search (of global blocks, heh). */
> if (result.symbol == NULL)
> - gdbarch_iterate_over_objfiles_in_search_order
> - (objfile != NULL ? objfile->arch () : current_inferior ()->arch (),
> - [&result, block_index, name, domain] (struct objfile *objfile_iter)
> - {
> - result = lookup_symbol_in_objfile (objfile_iter, block_index,
> - name, domain);
> - return result.symbol != nullptr;
> - },
> - objfile);
> + {
> + struct block_symbol result_unresolved = {};
> + gdbarch_iterate_over_objfiles_in_search_order
> + (objfile != NULL ? objfile->arch () : current_inferior ()->arch (),
> + [&result, &result_unresolved, block_index, name, domain]
> + (struct objfile *objfile_iter)
> + {
> + result = lookup_symbol_in_objfile (objfile_iter, block_index,
> + name, domain);
> + /* If RESULT is an unresolved function or method, keep track
> + of it in case no other symbols are found, but prefer
> + other matches over this one. */
> + if (result.symbol != nullptr
> + && result.symbol->aclass () == LOC_UNRESOLVED
> + && symbol_is_function_or_method (result.symbol))
> + {
> + result_unresolved = result;
> + result = {};
> + return false;
> + }
> + return result.symbol != nullptr;
> + },
> + objfile);
> +
> + if (result.symbol == nullptr)
> + result = result_unresolved;
> + }
>
> if (result.symbol != NULL)
> symbol_cache_mark_found (bsc, slot, objfile, result.symbol, result.block,
> @@ -5019,6 +5037,13 @@ global_symbol_searcher::add_matching_symbols
> && !treg_matches_sym_type_name (*treg, sym)))
> continue;
>
> + if ((kind & SEARCH_FUNCTION_DOMAIN) != 0)
> + {
> + /* Don't include unresolved function symbols. */
> + if (sym->aclass () == LOC_UNRESOLVED)
> + continue;
> + }
> +
> if ((kind & SEARCH_VAR_DOMAIN) != 0)
> {
> if (sym->aclass () == LOC_UNRESOLVED
> diff --git a/gdb/symtab.h b/gdb/symtab.h
> index 0a57be5ed80..461ca9d3a1e 100644
> --- a/gdb/symtab.h
> +++ b/gdb/symtab.h
> @@ -2494,6 +2494,23 @@ extern bool symbol_is_function_or_method (symbol *sym);
>
> extern bool symbol_is_function_or_method (minimal_symbol *msymbol);
>
> +/* Return whether SYM is an unresolved symbol. */
> +
> +static inline bool
> +completion_symbol_is_unresolved (symbol *sym)
> +{
> + return sym->aclass () == LOC_UNRESOLVED;
> +}
> +
> +/* For the purposes of skipping symbols for completion, return whether
> + MSYMBOL is unresolved. */
> +
> +static inline bool
> +completion_symbol_is_unresolved (minimal_symbol *msymbol)
> +{
> + return false;
> +}
> +
> /* Return whether SYM should be skipped in completion mode MODE. In
> linespec mode, we're only interested in functions/methods. */
>
> @@ -2502,7 +2519,8 @@ static bool
> completion_skip_symbol (complete_symbol_mode mode, Symbol *sym)
> {
> return (mode == complete_symbol_mode::LINESPEC
> - && !symbol_is_function_or_method (sym));
> + && (!symbol_is_function_or_method (sym)
> + || completion_symbol_is_unresolved (sym)));
> }
>
> /* symtab.c */
> diff --git a/gdb/testsuite/gdb.dwarf2/func-decl-lib.c b/gdb/testsuite/gdb.dwarf2/func-decl-lib.c
> new file mode 100644
> index 00000000000..4aad59be7ab
> --- /dev/null
> +++ b/gdb/testsuite/gdb.dwarf2/func-decl-lib.c
> @@ -0,0 +1,24 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> + Copyright 2025 Free Software Foundation, Inc.
> +
> + 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 <string.h>
> +
> +int
> +foo (char *s, int n)
> +{
> + return ((int) strlen (s) + n);
> +}
> diff --git a/gdb/testsuite/gdb.dwarf2/func-decl.c b/gdb/testsuite/gdb.dwarf2/func-decl.c
> new file mode 100644
> index 00000000000..62c49d5c456
> --- /dev/null
> +++ b/gdb/testsuite/gdb.dwarf2/func-decl.c
> @@ -0,0 +1,35 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> + Copyright 2025 Free Software Foundation, Inc.
> +
> + 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/>. */
> +
> +extern int foo (char *s, int n);
> +
> +volatile int data;
> +
> +void
> +use_it (int a)
> +{
> + data = a;
> +}
> +
> +int
> +main (int argc, char **argv)
> +{
> + asm ("main_label: .globl main_label");
> + int i;
> + i = foo ("foo", 2);
> + use_it (i);
> +}
> diff --git a/gdb/testsuite/gdb.dwarf2/func-decl.exp b/gdb/testsuite/gdb.dwarf2/func-decl.exp
> new file mode 100644
> index 00000000000..316dfe8d04c
> --- /dev/null
> +++ b/gdb/testsuite/gdb.dwarf2/func-decl.exp
> @@ -0,0 +1,182 @@
> +# Copyright 2025 Free Software Foundation, Inc.
> +# 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.
> +
> +# Test GDB's ability to access declarations for function symbols. On
> +# Linux, using GCC, compiling the main program with -g and the shared
> +# lib source file without -g can be used to create a compelling test
> +# case without needing the DWARF assembler. However, we don't want to
> +# count on the fact that the compiler will place a declaration for the
> +# shared library function in the DWARF info for the main program. (E.g.
> +# when using CLANG/LLVM, these DIEs are omitted.) Therefore, we use the
> +# DWARF assembler to create the requisite DWARF info for this test.
> +
> +load_lib dwarf.exp
> +require dwarf2_support
> +
> +standard_testfile .c -dw.S
> +
> +set asm_file [standard_output_file $srcfile2]
> +set libsrc "${srcdir}/${subdir}/${testfile}-lib.c"
> +set libobj [standard_output_file "${testfile}-lib.so"]
> +
> +# We need to know the size of integer and address types in order to
> +# write some of the debugging info we'd like to generate.
> +#
> +# For that, we ask GDB by debugging our test program. Any program
> +# would do, but since we already have program written specifically for
> +# this testcase, we might as well use that. Note that we need to
> +# also build the shared library that the test program uses.
> +
> +set session_options [list debug shlib=${libobj}]
> +if { [gdb_compile_shlib $libsrc $libobj [list nodebug]] != "" } {
> + untested "failed to compile shared object"
> + return -1
> +}
> +
> +if { [prepare_for_testing "failed to prepare" ${binfile} \
> + [list $srcfile] $session_options] } {
> + return -1
> +}
> +
> +with_test_prefix "first session" {
> + if ![runto_main] {
> + return
> + }
> +
> + with_shared_gdb {
> + # Rather than start a new session, declare the current session the
> + # shared one. Otherwise, get_func_info would compile an executable
> + # in a temp dir; due to implementation details, this means that the
> + # shared lib won't be found.
> + share_gdb ${srcdir}/${subdir}/$srcfile $session_options
> +
> + get_func_info main $session_options
> +
> + # Using the running GDB session, determine sizes of several types.
> + set int_size [get_sizeof "int" -1]
> + set char_ptr_size [get_sizeof "char *" 8]
> + }
> +}
> +
> +Dwarf::assemble $asm_file {
> + cu {} {
> + DW_TAG_compile_unit {
> + {DW_AT_language @DW_LANG_C99}
> + {DW_AT_name $::srcfile}
> + } {
> + declare_labels int_label char_label char_ptr_label
> +
> + int_label: DW_TAG_base_type {
> + {DW_AT_byte_size ${::int_size} DW_FORM_udata}
> + {DW_AT_encoding @DW_ATE_signed}
> + {DW_AT_name "int"}
> + }
> +
> + char_label: DW_TAG_base_type {
> + {byte_size 1 sdata}
> + {encoding @DW_ATE_signed_char}
> + {name "char"}
> + }
> +
> + char_ptr_label: DW_TAG_pointer_type {
> + {DW_AT_byte_size ${::char_ptr_size} DW_FORM_sdata}
> + {DW_AT_type :$char_label}
> + }
> +
> + DW_TAG_subprogram {
> + {DW_AT_external 1 flag}
> + {DW_AT_name foo}
> + {DW_AT_prototyped 1 DW_FORM_flag_present}
> + {DW_AT_type :$int_label}
> + {DW_AT_declaration 1 flag}
> + } {
> + DW_TAG_formal_parameter {
> + {DW_AT_type :$char_ptr_label}
> + }
> + DW_TAG_formal_parameter {
> + {DW_AT_type :$int_label}
> + }
> + }
> +
> + DW_TAG_subprogram {
> + {DW_AT_name main}
> + {DW_AT_low_pc ${::main_start} DW_FORM_addr}
> + {DW_AT_high_pc ${::main_end} DW_FORM_addr}
> + {DW_AT_type :$int_label}
> + }
> + }
> + }
> +}
> +
> +# Test against a shared library built with no debugging symbols. Due
> +# to the DWARF info provided by the DWARF assembler above, there will
> +# be a declaration for the shared lib symbol "foo" in the main
> +# program. Thus, due to the lack of DWARF info in the shared library,
> +# GDB can't know the type from the shared library. Instead, it must
> +# rely on the declaration of foo from the main program.
> +#
> +# Due to that declaration, it should be possible to examine its type
> +# as well as make an inferior function call. We expect "info addr foo"
> +# to provide the address of the actual function instead of foo's PLT
> +# in the main program.
> +
> +with_test_prefix no_lib_debug {
> + if { [gdb_compile_shlib $libsrc $libobj [list nodebug]] != "" } {
> + untested "failed to compile shared object"
> + return -1
> + }
> +
> + if { [prepare_for_testing "failed to prepare" ${binfile} \
> + [list $srcfile $asm_file] [list nodebug shlib=${libobj}]] } {
> + return -1
> + }
> +
> + with_test_prefix "before program start" {
> + # Verify that the type of foo is available prior to starting
> + # the program.
> + gdb_test "ptype foo" "^type = int \\(char \\*, int\\)"
> + }
> +
> + clean_restart $binfile
> +
> + if ![runto_main] {
> + return
> + }
> +
> + gdb_test "ptype foo" "^type = int \\(char \\*, int\\)"
> + gdb_test "print foo \(\"abc\", 5\)" "= 8"
> + gdb_test "info addr foo" "Symbol \"foo\" is at $::hex in a file compiled without debugging\\."
> +}
> +
> +# Test again with a library built with debugging symbols. The
> +# "info addr foo" test can fail if PLT symbols are preferred over
> +# normal symbols when looking up a global or static symbol.
> +
> +with_test_prefix lib_debug {
> + set binfile $binfile-debug
> + set libobj [standard_output_file "${testfile}-lib-debug.so"]
> +
> + if { [gdb_compile_shlib $libsrc $libobj [list debug]] != "" } {
> + untested "failed to compile shared object"
> + return -1
> + }
> +
> + if { [prepare_for_testing "failed to prepare" ${binfile} \
> + [list $srcfile $asm_file] [list nodebug shlib=${libobj}]] } {
> + return -1
> + }
> +
> + if ![runto_main] {
> + return
> + }
> +
> + gdb_test "info addr foo" "Symbol \"foo\" is a function at address $::hex\\."
> +}
> --
> 2.50.0
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v2] gdb/dwarf2: Add symbols for function declarations
2025-07-03 19:45 [PATCH v2] gdb/dwarf2: Add symbols for function declarations Kevin Buettner
` (3 preceding siblings ...)
2025-09-05 15:24 ` Andrew Burgess
@ 2025-09-05 15:38 ` Tom Tromey
2025-09-05 16:20 ` Simon Marchi
` (2 more replies)
4 siblings, 3 replies; 9+ messages in thread
From: Tom Tromey @ 2025-09-05 15:38 UTC (permalink / raw)
To: Kevin Buettner; +Cc: gdb-patches
>>>>> "Kevin" == Kevin Buettner <kevinb@redhat.com> writes:
Kevin> This version 2 commit
Kevin> adds that by making DW_TAG_subprogram declarations "interesting" to
Kevin> the indexer. The changes which do this are in gdb/dwarf2/abbrev.c
Kevin> and gdb/dwarf2/cooked-indexer.c.
I have some questions about this change.
How many symbols does it add, and what performance impact does it have?
I tend to think the indexer is fast primarily because it skips DIEs, but
IIUC this will have it read many more.
Do these declaration symbols end up in .debug_names and .gdb_index? I
suspect they should not. (Particularly for .debug_names which
specifically excludes them in the text of the standard.)
Does "break" on such a symbol result in many more CU expansions? If so
then I think this is a problem, because a declaration by its nature is
not useful for breakpoints.
What does "print malloc" say with this patch in place?
thanks,
Tom
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v2] gdb/dwarf2: Add symbols for function declarations
2025-09-05 15:38 ` Tom Tromey
@ 2025-09-05 16:20 ` Simon Marchi
2025-09-10 0:40 ` Tom Tromey
2025-09-13 0:55 ` Kevin Buettner
2 siblings, 0 replies; 9+ messages in thread
From: Simon Marchi @ 2025-09-05 16:20 UTC (permalink / raw)
To: Tom Tromey, Kevin Buettner; +Cc: gdb-patches
[-- Attachment #1: Type: text/plain, Size: 1343 bytes --]
On 9/5/25 11:38 AM, Tom Tromey wrote:
>>>>>> "Kevin" == Kevin Buettner <kevinb@redhat.com> writes:
>
> Kevin> This version 2 commit
> Kevin> adds that by making DW_TAG_subprogram declarations "interesting" to
> Kevin> the indexer. The changes which do this are in gdb/dwarf2/abbrev.c
> Kevin> and gdb/dwarf2/cooked-indexer.c.
>
> I have some questions about this change.
>
> How many symbols does it add, and what performance impact does it have?
> I tend to think the indexer is fast primarily because it skips DIEs, but
> IIUC this will have it read many more.
To measure this, I do:
$ ./gdb -nx -q --data-directory=data-directory -ex 'maint set dwarf sync on' -ex 'maintenance set per-command time on'
Then, use the "file" command.
For example, I ran gdb on libxul.so.debug without and with this patch
applied, see the two attached files.
The longest "DWARF indexing worker" step went from
wall 0.668, user 0.574, sys 0.093, user+sys 0.667, 99.9 % CPU
to
wall 0.792, user 0.664, sys 0.124, user+sys 0.788, 99.5 % CPU
The longest "DWARF finalize worker" step went from
wall 1.691, user 1.687, sys 0.003, user+sys 1.690, 99.9 % CPU
to
wall 3.650, user 3.626, sys 0.008, user+sys 3.634, 99.6 % CPU
The complete file command went from
13.375465 (cpu), 3.019934 (wall)
to
23.984434 (cpu), 5.163825 (wall)
Simon
[-- Attachment #2: before.txt --]
[-- Type: text/plain, Size: 2884 bytes --]
(gdb) file /usr/lib/debug/usr/lib/firefox/libxul.so.debug
2025-09-05 12:11:02.618 - command started
Reading symbols from /usr/lib/debug/usr/lib/firefox/libxul.so.debug...
Time for "minsyms install worker": wall 0.080, user 0.066, sys 0.012, user+sys 0.078, 97.5 % CPU
Time for "minsyms install worker": wall 0.091, user 0.074, sys 0.004, user+sys 0.078, 85.7 % CPU
Time for "minsyms install worker": wall 0.121, user 0.110, sys 0.010, user+sys 0.120, 99.2 % CPU
Time for "minsyms install worker": wall 0.135, user 0.104, sys 0.013, user+sys 0.117, 86.7 % CPU
Time for "minsyms install worker": wall 0.161, user 0.149, sys 0.011, user+sys 0.160, 99.4 % CPU
Time for "minsyms install worker": wall 0.175, user 0.151, sys 0.010, user+sys 0.161, 92.0 % CPU
Time for "minsyms install worker": wall 0.196, user 0.160, sys 0.017, user+sys 0.177, 90.3 % CPU
Time for "minsyms install worker": wall 0.212, user 0.190, sys 0.013, user+sys 0.203, 95.8 % CPU
Time for "DWARF indexing worker": wall 0.317, user 0.259, sys 0.055, user+sys 0.314, 99.1 % CPU
Time for "DWARF indexing worker": wall 0.379, user 0.340, sys 0.039, user+sys 0.379, 100.0 % CPU
Time for "DWARF indexing worker": wall 0.384, user 0.354, sys 0.029, user+sys 0.383, 99.7 % CPU
Time for "DWARF indexing worker": wall 0.431, user 0.376, sys 0.052, user+sys 0.428, 99.3 % CPU
Time for "DWARF indexing worker": wall 0.449, user 0.388, sys 0.061, user+sys 0.449, 100.0 % CPU
Time for "DWARF indexing worker": wall 0.454, user 0.409, sys 0.045, user+sys 0.454, 100.0 % CPU
Time for "DWARF indexing worker": wall 0.474, user 0.440, sys 0.032, user+sys 0.472, 99.6 % CPU
Time for "DWARF indexing worker": wall 0.668, user 0.574, sys 0.093, user+sys 0.667, 99.9 % CPU
Time for "DWARF skeletonless type units": wall 0.000, user 0.000, sys 0.000, user+sys 0.000, -nan % CPU
Time for "DWARF add parent map": wall 0.112, user 0.104, sys 0.008, user+sys 0.112, 100.0 % CPU
Time for "DWARF finalize worker": wall 0.646, user 0.643, sys 0.002, user+sys 0.645, 99.8 % CPU
Time for "DWARF finalize worker": wall 0.000, user 0.000, sys 0.000, user+sys 0.000, -nan % CPU
Time for "DWARF finalize worker": wall 0.784, user 0.780, sys 0.002, user+sys 0.782, 99.7 % CPU
Time for "DWARF finalize worker": wall 0.843, user 0.841, sys 0.001, user+sys 0.842, 99.9 % CPU
Time for "DWARF finalize worker": wall 0.872, user 0.865, sys 0.007, user+sys 0.872, 100.0 % CPU
Time for "DWARF finalize worker": wall 0.995, user 0.988, sys 0.006, user+sys 0.994, 99.9 % CPU
Time for "DWARF finalize worker": wall 1.121, user 1.119, sys 0.001, user+sys 1.120, 99.9 % CPU
Time for "DWARF finalize worker": wall 1.308, user 1.294, sys 0.013, user+sys 1.307, 99.9 % CPU
Time for "DWARF finalize worker": wall 1.691, user 1.687, sys 0.003, user+sys 1.690, 99.9 % CPU
2025-09-05 12:11:05.637 - command finished
Command execution time: 13.375465 (cpu), 3.019934 (wall)
[-- Attachment #3: after.txt --]
[-- Type: text/plain, Size: 2883 bytes --]
(gdb) file /usr/lib/debug/usr/lib/firefox/libxul.so.debug
2025-09-05 12:14:23.009 - command started
Reading symbols from /usr/lib/debug/usr/lib/firefox/libxul.so.debug...
Time for "minsyms install worker": wall 0.078, user 0.069, sys 0.009, user+sys 0.078, 100.0 % CPU
Time for "minsyms install worker": wall 0.090, user 0.073, sys 0.005, user+sys 0.078, 86.7 % CPU
Time for "minsyms install worker": wall 0.121, user 0.109, sys 0.011, user+sys 0.120, 99.2 % CPU
Time for "minsyms install worker": wall 0.135, user 0.110, sys 0.007, user+sys 0.117, 86.7 % CPU
Time for "minsyms install worker": wall 0.160, user 0.149, sys 0.010, user+sys 0.159, 99.4 % CPU
Time for "minsyms install worker": wall 0.174, user 0.158, sys 0.006, user+sys 0.164, 94.3 % CPU
Time for "minsyms install worker": wall 0.197, user 0.162, sys 0.014, user+sys 0.176, 89.3 % CPU
Time for "minsyms install worker": wall 0.210, user 0.189, sys 0.017, user+sys 0.206, 98.1 % CPU
Time for "DWARF indexing worker": wall 0.338, user 0.288, sys 0.049, user+sys 0.337, 99.7 % CPU
Time for "DWARF indexing worker": wall 0.464, user 0.418, sys 0.043, user+sys 0.461, 99.4 % CPU
Time for "DWARF indexing worker": wall 0.501, user 0.433, sys 0.065, user+sys 0.498, 99.4 % CPU
Time for "DWARF indexing worker": wall 0.585, user 0.507, sys 0.076, user+sys 0.583, 99.7 % CPU
Time for "DWARF indexing worker": wall 0.636, user 0.569, sys 0.064, user+sys 0.633, 99.5 % CPU
Time for "DWARF indexing worker": wall 0.646, user 0.569, sys 0.076, user+sys 0.645, 99.8 % CPU
Time for "DWARF indexing worker": wall 0.754, user 0.626, sys 0.126, user+sys 0.752, 99.7 % CPU
Time for "DWARF indexing worker": wall 0.792, user 0.664, sys 0.124, user+sys 0.788, 99.5 % CPU
Time for "DWARF skeletonless type units": wall 0.000, user 0.000, sys 0.000, user+sys 0.000, -nan % CPU
Time for "DWARF add parent map": wall 0.133, user 0.128, sys 0.004, user+sys 0.132, 99.2 % CPU
Time for "DWARF finalize worker": wall 0.885, user 0.882, sys 0.001, user+sys 0.883, 99.8 % CPU
Time for "DWARF finalize worker": wall 0.000, user 0.000, sys 0.000, user+sys 0.000, -nan % CPU
Time for "DWARF finalize worker": wall 1.376, user 1.372, sys 0.003, user+sys 1.375, 99.9 % CPU
Time for "DWARF finalize worker": wall 1.864, user 1.861, sys 0.002, user+sys 1.863, 99.9 % CPU
Time for "DWARF finalize worker": wall 2.240, user 2.235, sys 0.004, user+sys 2.239, 100.0 % CPU
Time for "DWARF finalize worker": wall 2.295, user 2.287, sys 0.005, user+sys 2.292, 99.9 % CPU
Time for "DWARF finalize worker": wall 2.470, user 2.467, sys 0.003, user+sys 2.470, 100.0 % CPU
Time for "DWARF finalize worker": wall 2.888, user 2.876, sys 0.011, user+sys 2.887, 100.0 % CPU
Time for "DWARF finalize worker": wall 3.650, user 3.626, sys 0.008, user+sys 3.634, 99.6 % CPU
2025-09-05 12:14:28.173 - command finished
Command execution time: 23.984434 (cpu), 5.163825 (wall)
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2] gdb/dwarf2: Add symbols for function declarations
2025-09-05 15:38 ` Tom Tromey
2025-09-05 16:20 ` Simon Marchi
@ 2025-09-10 0:40 ` Tom Tromey
2025-09-13 0:55 ` Kevin Buettner
2 siblings, 0 replies; 9+ messages in thread
From: Tom Tromey @ 2025-09-10 0:40 UTC (permalink / raw)
To: Tom Tromey; +Cc: Kevin Buettner, gdb-patches
I've been thinking about this some more.
Tom> Do these declaration symbols end up in .debug_names and .gdb_index? I
Tom> suspect they should not. (Particularly for .debug_names which
Tom> specifically excludes them in the text of the standard.)
This problem is a quandary, since if the symbols aren't added then there
will be behavior differences between the index and no-index cases. OTOH
adding the symbols means going against the DWARF standard (we could
document it I guess?) and also would mean maybe bumping the .gdb_index
version and/or updating gold (if anyone still cares).
I did think of a possible upside, which is that maybe this would let us
get rid of the DIE range map in the indexer, since I guess scanning
nameless subroutine entries would no longer matter.
Tom
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2] gdb/dwarf2: Add symbols for function declarations
2025-09-05 15:38 ` Tom Tromey
2025-09-05 16:20 ` Simon Marchi
2025-09-10 0:40 ` Tom Tromey
@ 2025-09-13 0:55 ` Kevin Buettner
2 siblings, 0 replies; 9+ messages in thread
From: Kevin Buettner @ 2025-09-13 0:55 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
On Fri, 05 Sep 2025 09:38:13 -0600
Tom Tromey <tom@tromey.com> wrote:
> >>>>> "Kevin" == Kevin Buettner <kevinb@redhat.com> writes:
>
> Kevin> This version 2 commit
> Kevin> adds that by making DW_TAG_subprogram declarations "interesting" to
> Kevin> the indexer. The changes which do this are in gdb/dwarf2/abbrev.c
> Kevin> and gdb/dwarf2/cooked-indexer.c.
>
> I have some questions about this change.
>
> How many symbols does it add, and what performance impact does it have?
> I tend to think the indexer is fast primarily because it skips DIEs, but
> IIUC this will have it read many more.
>
> Do these declaration symbols end up in .debug_names and .gdb_index? I
> suspect they should not. (Particularly for .debug_names which
> specifically excludes them in the text of the standard.)
>
> Does "break" on such a symbol result in many more CU expansions? If so
> then I think this is a problem, because a declaration by its nature is
> not useful for breakpoints.
I'm still gathering data and I'm trying to make sense of some of the
data already gathered, so I'm not quite ready to dive into too many
details yet.
However, using Simon's methodology, I've tested on 4 different x86_64
machines. On those machines, the total wall clock time increased from
between 6.9 to 9.2 percent, depending on machine. However, on my
(aarch64) macbook, the wall clock time increased by 267% !! I need to
understand why the macbook took so much longer...
This patch is of most value when library symbols are unavailable. So it
may make sense for there to be a setting to enable or disable loading
of declarations. An "auto" mode could, perhaps, cause loading of
declarations to be disabled when loading system libraries, but be
enabled for user libraries and the executable. And, even for the
latter category, GDB could check to see if (say) the C library has
symbols. If it does, then it could be disabled.
> What does "print malloc" say with this patch in place?
Without my patch:
(gdb) p malloc
$1 = {<text variable, no debug info>} 0x7ffff7d2fb10 <malloc>
With my patch:
(gdb) p malloc
$1 = {void *(unsigned long)} 0x7ffff7d2fb10 <malloc>
These were both run on the same machine, which does not have glibc
debuginfo installed, nor was debuginfod allowed to provide any
symbols.
^ permalink raw reply [flat|nested] 9+ messages in thread