[PATCH] RISC-V: Add --relax-verbose option for debuging linker relaxation
Nelson Chu
nelson@rivosinc.com
Wed Jul 2 07:00:20 GMT 2025
Well relaxations may be run multiple times, sometimes it works, sometimes
it doesn't, but finally it probably works at the end. So I don't think it
would be helpful to print out the entire trial and error process. In fact,
the reasons for relaxation failure are nothing more than the reserve size
and the max alignment. It shouldn't be hard to get these information from
symbol and relocation tables, and linker script.
Nelson
On Wed, Jul 2, 2025 at 2:27 PM Kito Cheng <kito.cheng@gmail.com> wrote:
> ping
>
>
> On Mon, Jun 2, 2025 at 10:06 PM Kito Cheng via Binutils
> <binutils@sourceware.org> wrote:
> >
> > 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
> >
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://sourceware.org/pipermail/binutils/attachments/20250702/2c5ba755/attachment-0001.htm>
More information about the Binutils
mailing list