]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
ld: BTF deduplication
authorNick Alcock <nick.alcock@oracle.com>
Fri, 25 Apr 2025 20:33:53 +0000 (21:33 +0100)
committerNick Alcock <nick.alcock@oracle.com>
Fri, 25 Apr 2025 20:33:53 +0000 (21:33 +0100)
Figuring out what to do when a mix of BTF and CTF sections is supplied is a
little magical.  We hunt down all the .BTF and .ctf sections in the link,
but just because all the sections we found were .BTF doesn't mean that the
output isn't going to be CTF: if deduplication results in any conflicting
types, we'll need a CTF section to encode them (BTF cannot yet represent
such things).

So if we find that we've got nothing but .BTF sections, we do as we do for
.ctf and mark all but one of them as excluded from the link (with the intent
of creating the deduplicated output in the remaining one): but we also
create a provisional linker-created .ctf section, just in case we need it
later (we can't tell at this stage, before deduplication).

After deduplication, one of these sections is unneeded.  Sometimes, we can
do this removal via Depending on the emulation, lang_write_ctf may be called
either early (long before bfd_elf_final_link) or late (after final link and
indeed symtab and strtab writeout).  (ELF calls it late).  If called early,
we can figure out what format was emitted by ctf_link and freely remove the
unwanted section by flipping on its SEC_EXCLUDE flag.

But that leaves late calls.  We add a new ctf_remove_section hook to the
bfd_link_callbacks, which is invoked at the very start of
bfd_elf_final_link, when removal of sections is still permitted.  We invoke
section removal for the .ctf-or-.BTF section via this hook if lang_write_ctf
is called late: this then does the extra trickery with section count
adjustment etc needed to remove sections so late, and communicates the fact
that it's removed sections to bfd_elf_final_link, which can then decide to
call _bfd_fix_excluded_sec_syms (which is quite expensive, so we do it only
once for all sections removed at this stage, by whatever means).

bfd/elflink.c
include/bfdlink.h
ld/ldlang.c
ld/ldlang.h
ld/ldmain.c

index 516a9e02d78027d25a9d8e3375304539d51b798b..16874cb8f8b81b86cc40e96155008e153add57f9 100644 (file)
@@ -12662,10 +12662,16 @@ bfd_elf_final_link (bfd *abfd, struct bfd_link_info *info)
                               sizeof (struct local_hash_entry)))
     return false;
 
+  sections_removed = false;
+
+  /* Give the CTF machinery a chance to remove sections from the link.  */
+  if (info->callbacks->ctf_remove_section
+      && !info->ctf_disabled)
+    sections_removed |= info->callbacks->ctf_remove_section ();
+
   /* The object attributes have been merged.  Remove the input
      sections from the link, and set the contents of the output
      section.  */
-  sections_removed = false;
   std_attrs_section = get_elf_backend_data (abfd)->obj_attrs_section;
   for (o = abfd->sections; o != NULL; o = o->next)
     {
@@ -12705,7 +12711,7 @@ bfd_elf_final_link (bfd *abfd, struct bfd_link_info *info)
          o->flags |= SEC_EXCLUDE;
          bfd_section_list_remove (abfd, o);
          abfd->section_count--;
-         sections_removed = true;
+         sections_removed |= true;
        }
     }
   if (sections_removed)
index bb1c651aa259b652048556234e9ff79642626f65..11b1606c0f2861d98cb0245a7e7c9024e365f4b8 100644 (file)
@@ -911,8 +911,15 @@ struct bfd_link_callbacks
   /* Likewise, for dynamic symbols.  */
   void (*ctf_new_dynsym)
     (int symidx, struct elf_internal_sym *sym);
-  /* This callback should emit the CTF section into a non-loadable section in
-     the output BFD named .ctf or a name beginning with ".ctf.".  */
+  /* This callback gives the CTF machinery a choice to decide which section
+     to emit its output into (and remove any others it may have added): it
+     is called at the last moment section removal is possible.  Returns 1
+     if any sections were removed.  */
+  int (*ctf_remove_section)
+    (void);
+  /* This callback should emit the CTF or BTF section into a non-loadable
+     section in the output BFD: its name has been decided in
+     ctf_remove_section above, likely .ctf or .BTF.  */
   void (*emit_ctf)
     (void);
 };
index a714be79d8e890a2985f0bd1862c658ba65aec9e..dd987b7ebcdbc84afec4052287cffb4987db53f1 100644 (file)
@@ -151,6 +151,7 @@ struct lang_nocrossrefs *nocrossref_list;
 struct asneeded_minfo **asneeded_list_tail;
 #ifdef ENABLE_LIBCTF
 static ctf_dict_t *ctf_output;
+static int try_pure_btf, is_pure_btf;
 #endif
 
 /* Functions that traverse the linker script and might evaluate
@@ -3802,7 +3803,10 @@ lang_ctf_errs_warnings (ctf_dict_t *fp)
 static void
 ldlang_open_ctf (void)
 {
-  int any_ctf = 0;
+  int any_cbtf = 0;
+  int all_btf = 1;
+  int picked_ctf = 0;
+  int picked_btf = 0;
   int err;
 
   if (link_info.ctf_disabled)
@@ -3829,25 +3833,69 @@ ldlang_open_ctf (void)
          continue;
        }
 
-      /* Prevent the contents of this section from being written, while
-        requiring the section itself to be duplicated in the output, but only
-        once.  */
-      /* This section must exist if ctf_bfdopen() succeeded.  */
-      sect = bfd_get_section_by_name (file->the_bfd, ".ctf");
+      /* Prevent the contents of this section from being written.  */
+
+      /* One of these sections must exist if ctf_bfdopen() succeeded.  */
+      if ((sect = bfd_get_section_by_name (file->the_bfd, ".ctf")) == NULL)
+       sect = bfd_get_section_by_name (file->the_bfd, ".BTF");
+      else
+       all_btf = 0;
+
       sect->size = 0;
-      sect->flags |= SEC_NEVER_LOAD | SEC_HAS_CONTENTS | SEC_LINKER_CREATED;
+      sect->flags |= SEC_NEVER_LOAD | SEC_HAS_CONTENTS | SEC_LINKER_CREATED
+       | SEC_EXCLUDE;
+      any_cbtf = 1;
 
-      if (any_ctf)
-       sect->flags |= SEC_EXCLUDE;
-      any_ctf = 1;
+      /* CTF section found: output must be CTF.  */
+      if (!all_btf && !picked_ctf)
+       {
+         sect->flags &= ~SEC_EXCLUDE;
+         picked_ctf = 1;
+       }
     }
 
-  if (!any_ctf)
+  if (!any_cbtf)
     {
       ctf_output = NULL;
       return;
     }
 
+  /* If we found no CTF sections, we may be generating BTF output: pick out a
+     BTF section to use for the output, and add a new CTF section -- which may
+     later be excluded -- to contain the CTF output if we later conclude we must
+     generate CTF in this case.  */
+
+  if (!picked_ctf)
+    {
+      try_pure_btf = 1;
+
+      LANG_FOR_EACH_INPUT_STATEMENT (dictfile)
+       {
+         asection *sect;
+
+         if ((sect = bfd_get_section_by_name (dictfile->the_bfd, ".BTF")) != NULL)
+           {
+             if (!picked_btf)
+               {
+                 sect->flags &= ~SEC_EXCLUDE;
+                 picked_btf = 1;
+               }
+             else
+               sect->flags |= SEC_EXCLUDE;
+           }
+
+         if (!picked_ctf)
+           {
+             bfd_make_section_with_flags (dictfile->the_bfd, ".ctf",
+                                          (SEC_NEVER_LOAD | SEC_HAS_CONTENTS
+                                           | SEC_LINKER_CREATED));
+             picked_ctf = 1;
+           }
+         if (picked_ctf && picked_btf)
+           break;
+       }
+    }
+
   if ((ctf_output = ctf_create (&err)) != NULL)
     return;
 
@@ -3864,16 +3912,17 @@ ldlang_open_ctf (void)
 static void
 lang_merge_ctf (void)
 {
-  asection *output_sect;
+  asection *btf_sect, *ctf_sect;
   int flags = 0;
 
   if (!ctf_output)
     return;
 
-  output_sect = bfd_get_section_by_name (link_info.output_bfd, ".ctf");
+  btf_sect = bfd_get_section_by_name (link_info.output_bfd, ".BTF");
+  ctf_sect = bfd_get_section_by_name (link_info.output_bfd, ".ctf");
 
-  /* If the section was discarded, don't waste time merging.  */
-  if (output_sect == NULL)
+  /* If all relevant sections were discarded, don't waste time merging.  */
+  if (ctf_sect == NULL && btf_sect == NULL)
     {
       ctf_dict_close (ctf_output);
       ctf_output = NULL;
@@ -3917,10 +3966,16 @@ lang_merge_ctf (void)
       einfo (_("%P: warning: CTF linking failed; "
               "output will have no CTF section: %s\n"),
             ctf_errmsg (ctf_errno (ctf_output)));
-      if (output_sect)
+
+      if (ctf_sect)
        {
-         output_sect->size = 0;
-         output_sect->flags |= SEC_EXCLUDE;
+         ctf_sect->size = 0;
+         ctf_sect->flags |= SEC_EXCLUDE;
+       }
+      if (btf_sect)
+       {
+         btf_sect->size = 0;
+         btf_sect->flags |= SEC_EXCLUDE;
        }
     }
   /* Output any lingering errors that didn't come from ctf_link.  */
@@ -3945,14 +4000,80 @@ void ldlang_ctf_new_dynsym (int symidx, struct elf_internal_sym *sym)
     ldemul_new_dynsym_for_ctf (ctf_output, symidx, sym);
 }
 
-/* Write out the CTF section.  Called early, if the emulation isn't going to
-   need to dedup against the strtab and symtab, then possibly called from the
-   target linker code if the dedup has happened.  */
+/* Remove all unused BTF/CTF sections from the link.  Return 1 if
+   _bfd_fix_excluded_sec_syms needs to be called.  */
+
+static int
+lang_write_ctf_remove_section (int late)
+{
+  asection *remove_sect;
+  int needs_exclude = 0;
+
+  if (!ctf_output)
+    return 0;
+
+  /* Figure out whether we're going to emit pure BTF or not.  */
+  if (try_pure_btf)
+    {
+      if ((is_pure_btf = ctf_link_output_is_btf (ctf_output)) < 0)
+       {
+         einfo (_("%P: cannot determine whether to emit BTF or CTF output: %s\n"),
+                  ctf_errmsg (ctf_errno (ctf_output)));
+         is_pure_btf = -1;
+         return 0;
+       }
+    }
+
+  /* Discard all instances of the section we're not outputting to.  */
+  if (is_pure_btf)
+    remove_sect = bfd_get_section_by_name (link_info.output_bfd, ".ctf");
+  else
+    remove_sect = bfd_get_section_by_name (link_info.output_bfd, ".BTF");
+
+  if (!remove_sect)
+    return 0;
+
+  do
+    {
+      if (remove_sect->flags & SEC_EXCLUDE)
+       continue;
+
+      remove_sect->size = 0;
+      remove_sect->flags |= SEC_EXCLUDE;
+
+      if (!late)
+       continue;
+
+      bfd_section_list_remove (link_info.output_bfd, remove_sect);
+      link_info.output_bfd->section_count--;
+      needs_exclude = 1;
+    } while ((remove_sect = bfd_get_next_section_by_name (NULL, remove_sect))
+            != NULL);
+
+  return needs_exclude;
+}
+
+/* Remove unused BTF/CTF sections late, if not already removed by an early
+   call.  */
+int
+ldlang_ctf_remove_section (void)
+{
+  return lang_write_ctf_remove_section (1);
+}
+
+/* Write out the BTF or CTF section.  Called early, if the emulation isn't
+   going to need to dedup against the strtab and symtab, then possibly
+   called from the target linker code if the dedup has happened.  */
 static void
 lang_write_ctf (int late)
 {
   size_t output_size;
+  asection *btf_sect, *ctf_sect;
   asection *output_sect;
+  size_t compression_threshold = CTF_COMPRESSION_THRESHOLD;
+  unsigned char *contents = NULL;
+  int really_btf;
+  int err = 0;
 
   if (!ctf_output)
     return;
@@ -3974,25 +4095,72 @@ lang_write_ctf (int late)
 
   ldemul_new_dynsym_for_ctf (ctf_output, 0, NULL);
 
-  /* Emit CTF.  */
+  /* If we are being called early, ldlang_ctf_remove_section has not yet
+     been called: do it by hand.  After this point, it's always been called,
+     either from here or from btf_elf_final_link.  */
+  if (!late)
+    lang_write_ctf_remove_section (late);
+
+  if (is_pure_btf < 0)
+    err = 1;
+  else if (is_pure_btf)
+    compression_threshold = (size_t) -1;
+
+  /* Finally serialize, taking note of whether what we actually generated was
+     CTF in the end.  Errors are handled below, if this section is actually
+     output.  */
+  if (!err)
+    contents = ctf_link_write (ctf_output, &output_size,
+                              compression_threshold, &really_btf);
+
+  /* Emit CTF or BTF, whichever was used and is needed.  We decide which to
+     emit to based on the decision taken by section removal, above, not
+     based on what ctf_link_write eventually emitted.  */
+
+  btf_sect = bfd_get_section_by_name (link_info.output_bfd, ".BTF");
+  ctf_sect = bfd_get_section_by_name (link_info.output_bfd, ".ctf");
+
+  if (is_pure_btf)
+    output_sect = btf_sect;
+  else
+    output_sect = ctf_sect;
+
+  /* Complain and fail if we thought we were emitting BTF but now it's been
+     upgraded to CTF (which is not supposed to happen, though it's perfectly
+     fine to go the other way).  */
+  if (!err && is_pure_btf && !really_btf)
+    {
+      einfo (_("%P: warning: BTF section %pA unexpectedly upgraded to CTF "
+              "after section assignment: "
+              "output will have no BTF or CTF sections\n"),
+            output_sect);
+      free (contents);
+      contents = NULL;
+      err = 1;
+    }
 
-  output_sect = bfd_get_section_by_name (link_info.output_bfd, ".ctf");
   if (output_sect)
     {
-      output_sect->contents = ctf_link_write (ctf_output, &output_size,
-                                             CTF_COMPRESSION_THRESHOLD);
+      output_sect->contents = contents;
       output_sect->size = output_size;
       output_sect->flags |= SEC_IN_MEMORY | SEC_KEEP;
+      output_sect->flags &= ~SEC_EXCLUDE;
 
       lang_ctf_errs_warnings (ctf_output);
+
       if (!output_sect->contents)
        {
-         einfo (_("%P: warning: CTF section emission failed; "
-                  "output will have no CTF section: %s\n"),
+         einfo (_("%P: warning: %s section emission failed; "
+                  "output will have no %s section: %s\n"),
+                is_pure_btf ? "BTF" : "CTF", is_pure_btf ? ".BTF" : ".ctf",
                 ctf_errmsg (ctf_errno (ctf_output)));
-         output_sect->size = 0;
-         output_sect->flags |= SEC_EXCLUDE;
        }
+
+      /* There's nothing we can really do on error at this stage: BFD is
+        committed to emitting the section.  Just leave it be and make sure
+        it's empty.  */
+      if (err)
+       output_sect->size = 0;
     }
 
   /* This also closes every CTF input file used in the link.  */
@@ -4023,14 +4191,14 @@ ldlang_open_ctf (void)
     {
       asection *sect;
 
-      /* If built without CTF, warn and delete all CTF sections from the output.
-        (The alternative would be to simply concatenate them, which does not
-        yield a valid CTF section.)  */
+      /* If built without CTF or BTF, warn and delete all CTF sections from the
+        output.  */
 
-      if ((sect = bfd_get_section_by_name (file->the_bfd, ".ctf")) != NULL)
+      if (((sect = bfd_get_section_by_name (file->the_bfd, ".ctf")) != NULL) ||
+         ((sect = bfd_get_section_by_name (file->the_bfd, ".BTF")) != NULL))
        {
-           einfo (_("%P: warning: CTF section in %pB not linkable: "
-                    "%P was built without support for CTF\n"), file->the_bfd);
+           einfo (_("%P: warning: BTF or CTF section in %pB not linkable: "
+                    "%P was built without support for libctf\n"), file->the_bfd);
            sect->size = 0;
            sect->flags |= SEC_EXCLUDE;
        }
@@ -8572,7 +8740,7 @@ lang_process (void)
        }
     }
 
-  /* Merge together CTF sections.  After this, only the symtab-dependent
+  /* Merge together CTF and BTF sections.  After this, only the symtab-dependent
      function and data object sections need adjustment.  */
   lang_merge_ctf ();
 
index 8d905f047423260df46744b294ef6fb48af5603b..de6adda9953cc47163eccd8d6a576701766d8eda 100644 (file)
@@ -734,6 +734,8 @@ extern void ldlang_ctf_acquire_strings
   (struct elf_strtab_hash *);
 extern void ldlang_ctf_new_dynsym
   (int symidx, struct elf_internal_sym *);
+extern int ldlang_ctf_remove_section
+  (void);
 extern void ldlang_write_ctf_late
   (void);
 extern bool
index 54a834e42a61ff03cbcf8cba3c435b238c91549b..7c0da0d70d3ff17d459dc208b5327f37e0b48c5a 100644 (file)
@@ -156,6 +156,7 @@ static struct bfd_link_callbacks link_callbacks =
   ldlang_ctf_acquire_strings,
   NULL,
   ldlang_ctf_new_dynsym,
+  ldlang_ctf_remove_section,
   ldlang_write_ctf_late
 };