[PATCH] RISC-V: Add --relax-verbose option for debuging linker relaxation

Kito Cheng kito.cheng@sifive.com
Mon Jun 2 14:05:30 GMT 2025
We need spend lots of time to figure out what happened when we found a symbol
isn't get relaxed, so...why not add a verbose option for linker relaxation?

It's not cover all kind of relaxation yet, but covered most common use case:
function call relaxation and GP relaxation.

This would be useful for debugging linker relaxation issues, especially
when we doing Zcmt and landing pad relaxation...(those are not
upstreamed yet.).

Output example:

$ riscv64-unknown-elf-gcc ~/hello.c -Wl,--relax-verbose
...
relax verbose: libc_a-puts.o: .text._puts_r Symbol strlen reserve_size is 0, symbol size is 154.
relax verbose: libc_a-puts.o: .text._puts_r Function call relaxation success, relax to j[al] (target: strlen, offset: 1386).
relax verbose: libc_a-puts.o: .text._puts_r Symbol .LC0 reserve_size is 0, symbol size is 0.
relax verbose: libc_a-puts.o: .text._puts_r GP relaxation success(target: .LC0).
relax verbose: libc_a-puts.o: .text._puts_r Symbol .LC0 reserve_size is 0, symbol size is 0.
relax verbose: libc_a-puts.o: .text._puts_r Symbol __sfvwrite_r reserve_size is 0, symbol size is 914.
relax verbose: libc_a-puts.o: .text._puts_r Function call relaxation success, relax to j[al] (target: __sfvwrite_r, offset: 5198).
relax verbose: libc_a-puts.o: .text._puts_r Symbol __sinit reserve_size is 0, symbol size is 22.
relax verbose: libc_a-puts.o: .text._puts_r Function call relaxation success, relax to j[al] (target: __sinit, offset: -542).
relax verbose: libc_a-puts.o: .text.puts Symbol _impure_ptr reserve_size is 8, symbol size is 8.
relax verbose: libc_a-puts.o: .text.puts GP relaxation success(target: _impure_ptr).
relax verbose: libc_a-puts.o: .text.puts Symbol _impure_ptr reserve_size is 8, symbol size is 8.
relax verbose: libc_a-puts.o: .text.puts Symbol _puts_r reserve_size is 8, symbol size is 132.
...
---
 bfd/elfnn-riscv.c        | 195 ++++++++++++++++++++++++++++++++++++++-
 bfd/elfxx-riscv.h        |   2 +
 ld/emultempl/riscvelf.em |   6 ++
 ld/ldlex.h               |   1 +
 4 files changed, 201 insertions(+), 3 deletions(-)

diff --git a/bfd/elfnn-riscv.c b/bfd/elfnn-riscv.c
index 1c494f5f986..9623e6833f6 100644
--- a/bfd/elfnn-riscv.c
+++ b/bfd/elfnn-riscv.c
@@ -252,11 +252,15 @@ struct riscv_elf_link_hash_table
     && elf_hash_table_id (elf_hash_table (p)) == RISCV_ELF_DATA)	\
    ? (struct riscv_elf_link_hash_table *) (p)->hash : NULL)
 
+/* Print verbose info when linker relaxation.  */
+static bool relax_verbose = false;
+
 void
 riscv_elfNN_set_options (struct bfd_link_info *link_info,
 			 struct riscv_elf_params *params)
 {
   riscv_elf_hash_table (link_info)->params = params;
+  relax_verbose = params->relax_verbose;
 }
 
 static bool
@@ -292,6 +296,85 @@ riscv_is_insn_reloc (const reloc_howto_type *howto)
 	       ? (MINUS_ONE << howto->bitsize) : (bfd_vma)0)) != 0);
 }
 
+/* Return true if the given symbol index is a local symbol.  */
+static inline bool
+riscv_is_local_symbol (Elf_Internal_Shdr *symtab_hdr, unsigned long symndx)
+{
+  /* Document:
+     https://docs.oracle.com/cd/E19683-01/816-7529/chapter6-79797/index.html
+
+     > a symbol table section's sh_info section header member holds the
+     > symbol table index for the first non-local symbol.  */
+  return symndx < symtab_hdr->sh_info;
+}
+
+static size_t
+riscv_get_symbol_size (bfd *abfd, unsigned long symndx)
+{
+  Elf_Internal_Shdr *symtab_hdr = &elf_symtab_hdr (abfd);
+
+  if (!symtab_hdr->contents)
+    return 0;
+
+  if (symndx < symtab_hdr->sh_info)
+    {
+      /* A local symbol.  */
+      Elf_Internal_Sym *sym
+	= ((Elf_Internal_Sym *) symtab_hdr->contents + symndx);
+      return sym->st_size;
+    }
+  else
+    {
+      struct elf_link_hash_entry *h;
+      unsigned indx = symndx - symtab_hdr->sh_info;
+      h = elf_sym_hashes (abfd)[indx];
+      while (h->root.type == bfd_link_hash_indirect
+	     || h->root.type == bfd_link_hash_warning)
+	h = (struct elf_link_hash_entry *) h->root.u.i.link;
+
+      if (h != NULL && h->type != STT_GNU_IFUNC)
+       return h->size;
+      else
+       /* We do not handle STT_GNU_IFUNC currently.  */
+       return 0;
+    }
+}
+
+static const char *
+riscv_get_symbol_name (bfd *abfd, unsigned long symndx)
+{
+  Elf_Internal_Shdr *symtab_hdr = &elf_symtab_hdr (abfd);
+  const char *name;
+
+  if (!symtab_hdr->contents)
+    return NULL;
+
+  if (symndx < symtab_hdr->sh_info)
+    {
+      /* A local symbol.  */
+      Elf_Internal_Sym *sym
+	  = ((Elf_Internal_Sym *) symtab_hdr->contents + symndx);
+      name = bfd_elf_sym_name (abfd, symtab_hdr, sym, NULL);
+    }
+  else
+    {
+      struct elf_link_hash_entry *h;
+      unsigned indx = symndx - symtab_hdr->sh_info;
+      h = elf_sym_hashes (abfd)[indx];
+      while (h->root.type == bfd_link_hash_indirect
+	     || h->root.type == bfd_link_hash_warning)
+	h = (struct elf_link_hash_entry *) h->root.u.i.link;
+
+      if (h != NULL && h->type != STT_GNU_IFUNC)
+	name = h->root.root.string;
+      else
+	/* We do not handle STT_GNU_IFUNC currently.  */
+	return NULL;
+    }
+
+  return name;
+}
+
 /* PLT/GOT stuff.  */
 #define PLT_HEADER_INSNS 8
 #define PLT_ENTRY_INSNS 4
@@ -4284,6 +4367,24 @@ riscv_elf_obj_attrs_handle_unknown (bfd *abfd, int tag)
   return true;
 }
 
+/* Relax verbose function.  */
+static void
+_riscv_verbose_relax (bfd *abfd, asection *sec, const char *fmt, ...)
+{
+  if (!relax_verbose)
+    return;
+
+  va_list args;
+  va_start (args, fmt);
+  fprintf (stderr, "relax verbose: %s: %s ", bfd_get_filename (abfd),
+	   bfd_section_name (sec));
+
+  vfprintf (stderr, fmt, args);
+
+  fprintf (stderr, "\n");
+  va_end (args);
+}
+
 /* A second format for recording PC-relative hi relocations.  This stores the
    information required to relax them to GP-relative addresses.  */
 
@@ -4690,6 +4791,8 @@ _bfd_riscv_relax_call (bfd *abfd, asection *sec, asection *sym_sec,
   bool near_zero = (symval + RISCV_IMM_REACH / 2) < RISCV_IMM_REACH;
   bfd_vma auipc, jalr;
   int rd, r_type, len = 4, rvc = elf_elfheader (abfd)->e_flags & EF_RISCV_RVC;
+  unsigned symidx = ELFNN_R_SYM (rel->r_info);
+  const char *sym_str = riscv_get_symbol_name (abfd, symidx);
 
   /* If the call crosses section boundaries, an alignment directive could
      cause the PC-relative offset to later increase, so we need to add in the
@@ -4705,7 +4808,15 @@ _bfd_riscv_relax_call (bfd *abfd, asection *sec, asection *sym_sec,
 
   /* See if this function call can be shortened.  */
   if (!VALID_JTYPE_IMM (foff) && !(!bfd_link_pic (link_info) && near_zero))
-    return true;
+    {
+      uint64_t pc = sec_addr (sec) + rel->r_offset;
+      _riscv_verbose_relax (
+	  abfd, sec,
+	  "Function call relaxation fail due to range too far "
+	  "(pc: %" PRIx64 " target: %s (%" PRIx64 "), offset: %" PRId64 ").",
+	  (uint64_t) pc, sym_str, (uint64_t) symval, (int64_t) foff);
+      return true;
+    }
 
   /* Shorten the function call.  */
   BFD_ASSERT (rel->r_offset + 8 <= sec->size);
@@ -4718,24 +4829,28 @@ _bfd_riscv_relax_call (bfd *abfd, asection *sec, asection *sym_sec,
   /* C.J exists on RV32 and RV64, but C.JAL is RV32-only.  */
   rvc = rvc && (rd == 0 || (rd == X_RA && ARCH_SIZE == 32));
 
+  const char *relax_type;
   if (rvc)
     {
       /* Relax to C.J[AL] rd, addr.  */
       r_type = R_RISCV_RVC_JUMP;
       auipc = rd == 0 ? MATCH_C_J : MATCH_C_JAL;
       len = 2;
+      relax_type = "c.j[al]";
     }
   else if (VALID_JTYPE_IMM (foff))
     {
       /* Relax to JAL rd, addr.  */
       r_type = R_RISCV_JAL;
       auipc = MATCH_JAL | (rd << OP_SH_RD);
+      relax_type = "j[al]";
     }
   else
     {
       /* Near zero, relax to JALR rd, x0, addr.  */
       r_type = R_RISCV_LO12_I;
       auipc = MATCH_JALR | (rd << OP_SH_RD);
+      relax_type = "zero page relaxation";
     }
 
   /* Replace the R_RISCV_CALL reloc.  */
@@ -4745,6 +4860,12 @@ _bfd_riscv_relax_call (bfd *abfd, asection *sec, asection *sym_sec,
 
   /* Delete unnecessary JALR and reuse the R_RISCV_RELAX reloc.  */
   *again = true;
+
+  _riscv_verbose_relax (abfd, sec,
+			"Function call relaxation success, relax to %s "
+			"(target: %s, offset: %" PRId64 ").",
+			relax_type, sym_str, (int64_t) foff);
+
   return riscv_relax_delete_bytes (abfd, sec, rel->r_offset + len, 8 - len,
 				   link_info, pcgp_relocs, rel + 1);
 }
@@ -4836,6 +4957,8 @@ _bfd_riscv_relax_lui (bfd *abfd,
 			? data_segment_alignment : max_alignment;
     }
 
+  unsigned symidx = ELFNN_R_SYM (rel->r_info);
+  const char *sym_str = riscv_get_symbol_name (abfd, symidx);
   /* Is the reference in range of x0 or gp?
      Valid gp range conservatively because of alignment issue.
 
@@ -4861,6 +4984,10 @@ _bfd_riscv_relax_lui (bfd *abfd,
 	case R_RISCV_HI20:
 	  /* Delete unnecessary LUI and reuse the reloc.  */
 	  *again = true;
+	  _riscv_verbose_relax (abfd, sec,
+				"GP relaxation success"
+				"(target: %s).",
+				sym_str);
 	  return riscv_relax_delete_bytes (abfd, sec, rel->r_offset, 4,
 					   link_info, pcgp_relocs, rel);
 
@@ -4894,6 +5021,10 @@ _bfd_riscv_relax_lui (bfd *abfd,
 
       /* Delete extra bytes and reuse the R_RISCV_RELAX reloc.  */
       *again = true;
+      _riscv_verbose_relax (abfd, sec,
+			    "GP relaxation success"
+			    "(target: %s).",
+			    sym_str);
       return riscv_relax_delete_bytes (abfd, sec, rel->r_offset + 2, 2,
 				       link_info, pcgp_relocs, rel + 1);
     }
@@ -5029,6 +5160,8 @@ _bfd_riscv_relax_pc (bfd *abfd ATTRIBUTE_UNUSED,
 
   BFD_ASSERT (rel->r_offset + 4 <= sec->size);
 
+  unsigned symidx = ELFNN_R_SYM (rel->r_info);
+  const char *sym_str = riscv_get_symbol_name (abfd, symidx);
   /* Chain the _LO relocs to their cooresponding _HI reloc to compute the
      actual target address.  */
   riscv_pcgp_hi_reloc hi_reloc;
@@ -5067,12 +5200,31 @@ _bfd_riscv_relax_pc (bfd *abfd ATTRIBUTE_UNUSED,
       /* Mergeable symbols and code might later move out of range.  */
       if (! undefined_weak
 	  && sym_sec->flags & (SEC_MERGE | SEC_CODE))
-	return true;
+	{
+	  if (sym_sec->flags & SEC_MERGE)
+	    _riscv_verbose_relax (
+		abfd, sec, "GP relaxation failed due to `%s` is SEC_MERGE.",
+		sym_str);
+	  if (sym_sec->flags & SEC_CODE)
+	    _riscv_verbose_relax (
+		abfd, sec,
+		"GP relaxation failed due to `%s` the symbol is SEC_CODE.",
+		sym_str);
+
+	  return true;
+	}
 
       /* If the cooresponding lo relocation has already been seen then it's not
          safe to relax this relocation.  */
       if (riscv_find_pcgp_lo_reloc (pcgp_relocs, rel->r_offset))
-	return true;
+	{
+	  _riscv_verbose_relax (
+	      abfd, sec,
+	      "GP relaxation failed due to R_RISCV_PCREL_LO12_* "
+	      "before R_RISCV_PCREL_HI20");
+
+	  return true;
+	}
 
       break;
 
@@ -5148,6 +5300,11 @@ _bfd_riscv_relax_pc (bfd *abfd ATTRIBUTE_UNUSED,
 	  *again = true;
 	  riscv_relax_delete_bytes (abfd, sec, rel->r_offset, 4, link_info,
 				    pcgp_relocs, rel);
+	  _riscv_verbose_relax (abfd, sec,
+				"GP relaxation success"
+				"(target: %s, offset: %" PRId64 ").",
+				sym_str, 0);
+
 	  return true;
 
 	default:
@@ -5155,6 +5312,29 @@ _bfd_riscv_relax_pc (bfd *abfd ATTRIBUTE_UNUSED,
 	}
     }
 
+  if (gp && VALID_ITYPE_IMM (symval - gp))
+    {
+      _riscv_verbose_relax (
+	  abfd, sec,
+	  "GP relaxation failed due to max_alignment or reserve_size"
+	  "(target: %s, gp: 0x%" PRIx64 " sym addr: 0x%" PRIx64 ", "
+	  "distance: %" PRId64 ", max_alignment: %" PRId64
+	  ", reserve_size: %" PRId64 ").",
+	  sym_str, gp, symval, gp - symval, max_alignment, reserve_size);
+    }
+  else if (gp && !VALID_ITYPE_IMM (symval - gp))
+    _riscv_verbose_relax (abfd, sec,
+			  "GP relaxation failed due to far from GP"
+			  "(target: %s, gp: 0x%" PRIx64 " sym addr: 0x%" PRIx64
+			  ", distance: %" PRId64 ").",
+			  sym_str, gp, symval, gp - symval);
+  else
+    _riscv_verbose_relax (abfd, sec,
+			  "GP relaxation failed due to unknwon reason"
+			  "(target: %s, gp: 0x%" PRIx64 " sym addr: 0x%" PRIx64
+			  ", distance: %" PRId64 ").",
+			  sym_str, gp, symval, gp - symval);
+
   return true;
 }
 
@@ -5435,6 +5615,15 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec,
 	symval += rel->r_addend;
 
       symval += sec_addr (sym_sec);
+      unsigned symidx = ELFNN_R_SYM (rel->r_info);
+      const char *sym_str = riscv_get_symbol_name (abfd, symidx);
+      size_t *sym_size = riscv_get_symbol_size (abfd, symidx);
+
+      _riscv_verbose_relax (abfd, sec,
+			    "Symbol %s "
+			    "reserve_size is %" PRId64
+			    ", symbol size is %" PRId64 ".",
+			    sym_str, reserve_size, sym_size);
 
       if (!relax_func (abfd, sec, sym_sec, info, rel, symval,
 		       max_alignment, reserve_size, again,
diff --git a/bfd/elfxx-riscv.h b/bfd/elfxx-riscv.h
index 1ce682a97ac..03bdb7d26f7 100644
--- a/bfd/elfxx-riscv.h
+++ b/bfd/elfxx-riscv.h
@@ -33,6 +33,8 @@ struct riscv_elf_params
   bool relax_gp;
   /* Whether to check if SUB_ULEB128 relocation has non-zero addend.  */
   bool check_uleb128;
+  /* Verbose mode for linker relaxation.  */
+  bool relax_verbose;
 };
 
 extern void riscv_elf32_set_options (struct bfd_link_info *,
diff --git a/ld/emultempl/riscvelf.em b/ld/emultempl/riscvelf.em
index 78e1fcde68e..cf75e474e90 100644
--- a/ld/emultempl/riscvelf.em
+++ b/ld/emultempl/riscvelf.em
@@ -36,6 +36,7 @@ PARSE_AND_LIST_LONGOPTS=${PARSE_AND_LIST_LONGOPTS}'
     { "no-relax-gp", no_argument, NULL, OPTION_NO_RELAX_GP },
     { "check-uleb128", no_argument, NULL, OPTION_CHECK_ULEB128 },
     { "no-check-uleb128", no_argument, NULL, OPTION_NO_CHECK_ULEB128 },
+    { "relax-verbose", no_argument, NULL, OPTION_RELAX_VERBOSE },
 '
 
 PARSE_AND_LIST_OPTIONS=${PARSE_AND_LIST_OPTIONS}'
@@ -43,6 +44,7 @@ PARSE_AND_LIST_OPTIONS=${PARSE_AND_LIST_OPTIONS}'
   fprintf (file, _("  --no-relax-gp               Don'\''t perform GP relaxation\n"));
   fprintf (file, _("  --check-uleb128             Check if SUB_ULEB128 has non-zero addend\n"));
   fprintf (file, _("  --no-check-uleb128          Don'\''t check if SUB_ULEB128 has non-zero addend\n"));
+  fprintf (file, _("  --relax-verbose             Verbose output for linker relaxation\n"));
 '
 
 PARSE_AND_LIST_ARGS_CASES=${PARSE_AND_LIST_ARGS_CASES}'
@@ -61,6 +63,10 @@ PARSE_AND_LIST_ARGS_CASES=${PARSE_AND_LIST_ARGS_CASES}'
     case OPTION_NO_CHECK_ULEB128:
       params.check_uleb128 = 0;
       break;
+
+    case OPTION_RELAX_VERBOSE:
+      params.relax_verbose = 1;
+      break;
 '
 
 fragment <<EOF
diff --git a/ld/ldlex.h b/ld/ldlex.h
index 815da76a4c0..72db6fbb08c 100644
--- a/ld/ldlex.h
+++ b/ld/ldlex.h
@@ -427,6 +427,7 @@ enum option_values
   OPTION_NO_RELAX_GP,
   OPTION_CHECK_ULEB128,
   OPTION_NO_CHECK_ULEB128,
+  OPTION_RELAX_VERBOSE,
   /* Used by emultempl/rxelf.em.  */
   OPTION_NO_FLAG_MISMATCH_WARNINGS,
   OPTION_IGNORE_LMA,
-- 
2.34.1



More information about the Binutils mailing list