[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