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

Jiawei jiawei@iscas.ac.cn
Wed Jun 4 05:29:24 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,
>
LGTM, thanks.

Jiawei



More information about the Binutils mailing list