[PATCH] RISC-V: Add --relax-verbose option for debuging linker relaxation
Fangrui Song
i@maskray.me
Wed Jul 2 07:39:24 GMT 2025
On Wed, Jul 2, 2025 at 12:01 AM Nelson Chu <nelson@rivosinc.com> wrote:
>
> 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
Agreed. Generally diagnostics and output should be suppressed for all
but the last iteration...
I recently resolved an oscillation bug in LLD
(https://github.com/llvm/llvm-project/pull/142899 ).
The output from this patch would not be sufficient for debugging. I
wrote targeted, one-off dump code to diagnose the bug, taking care to
avoid excessive output since the executable was large...
I believe broadly applicable dump code is challenging.
> 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
>> >
More information about the Binutils
mailing list