Bug 28709 - [RISC-V] a unused section can't be removed with -gc-sections
Summary: [RISC-V] a unused section can't be removed with -gc-sections
Status: RESOLVED FIXED
Alias: None
Product: binutils
Classification: Unclassified
Component: ld (show other bugs)
Version: 2.38
: P2 normal
Target Milestone: 2.38
Assignee: Alan Modra
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2021-12-17 06:47 UTC by lifang_xia
Modified: 2022-01-13 08:40 UTC (History)
1 user (show)

See Also:
Host:
Target: 2021-12-20 0:00
Build:
Last reconfirmed: 2021-12-20 00:00:00


Attachments
source code and command (2.24 KB, application/x-gzip)
2021-12-17 06:47 UTC, lifang_xia
Details
gas sorting of relocs (2.24 KB, patch)
2021-12-20 14:48 UTC, Alan Modra
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description lifang_xia 2021-12-17 06:47:58 UTC
Created attachment 13860 [details]
source code and command

I have met a situation, but I think it should be bug for ld in RISC-V.
A unused section can't be removed with gc-section.

The attachment contains the sources and command to run:
a.c: a main entry, which only prints "HelloWorld", it does not depend on b.cpp.
a.s: a asm file created by compiling a.c
b.cpp: a cpp with a class, and a function
b.s: a asm file created by compiling b.cpp
b.simple.s: delete some sections from b.s
===================
BUILD
riscv64-unknown-elf-gcc -ffunction-sections -fdata-sections -o a.o a.c -c
riscv64-unknown-elf-g++ -ffunction-sections -fdata-sections -o b.o b.cpp -c
riscv64-unknown-elf-g++ -Wl,-gc-sections -o a a.o b.o

I can find the test111111 in the output with "readelf -sW a".
Obviously, test111111 should be removed. I have tested on ARM and x86, both of them can remove test111111.

b.simple.s is simpler than b.s, I use the gas and ld to do test:
./gas/as-new -o a.o a.s
./gas/as-new -o b.o b.simple.s
./ld/ld-new  -o a a.o b.o -e main --defsym=puts=0 -gc-section --print-gc-sections

==========================================
the reason why test111111 can't be removed is that there is a init_array in b.simple.s which holds a entry _GLOBAL__sub_I__ZN3Box3getEv,
and _GLOBAL__sub_I__ZN3Box3getEv have a eh_frame. there are only one eh_frame in b.o, so eh_frame also contains the dwarf about test111111.

The gas create some relocs for DW_CFA_advance_loc* which used in linker relax. The dest symbol of relocs belongs to .text.test111111.

So ld can't remove the .text.test111111.

=========================================
How to solve

1. Skip gc_mark in riscv_elf_gc_mark_hook
+  /* Ignore the reloc which are used for eh_frame. */
+  if (strcmp(sec->name, ".eh_frame") == 0)
+    {
+      switch (ELFNN_R_TYPE (rel->r_info))
+      {
+      case R_RISCV_SET6:
+      case R_RISCV_SUB6:
+      case R_RISCV_SET8:
+      case R_RISCV_SUB8:
+      case R_RISCV_SET16:
+      case R_RISCV_SUB16:
+      case R_RISCV_SET32:
+      case R_RISCV_SUB32:
+        return NULL;
+      }
+    }

2, Split .eh_frame to .eh_frame.function. It is a good choice, but the dwarf may not be suitable for that.
Comment 1 Alan Modra 2021-12-20 05:48:11 UTC
The problem is actually that you have out-of-order relocations in .eh_frame.

Relocation section '.rela.eh_frame' at offset 0x538 contains 8 entries:
    Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
000000000000001c  0000000800000039 R_RISCV_32_PCREL       0000000000000000 .L0  + 0
0000000000000020  0000000c00000023 R_RISCV_ADD32          000000000000003e .L0  + 0
0000000000000020  0000000800000027 R_RISCV_SUB32          0000000000000000 .L0  + 0
0000000000000040  0000000f00000039 R_RISCV_32_PCREL       0000000000000000 .L0  + 0
0000000000000044  0000001100000023 R_RISCV_ADD32          0000000000000002 .L0  + 0
0000000000000044  0000000f00000027 R_RISCV_SUB32          0000000000000000 .L0  + 0
000000000000002f  0000000b00000035 R_RISCV_SET6           000000000000003a .L0  + 0
000000000000002f  0000000a00000034 R_RISCV_SUB6           0000000000000006 .L0  + 0

When the linker is marking the FDE for _GLOBAL__sub_I__ZN3Box3getEv it starts correctly at the r_offset 0x40 reloc marking that and the two r_offset 0x44 relocs.  Those all point at _GLOBAL__sub_I__ZN3Box3getEv. (Watch out for tricky multiple .L0 symbols, it's easy to get confused over which section they belong to!  You need to look at the index in r_info.)  Then the two out-of-order relocs at 0x2f are encountered, and processed as if they are part of the _GLOBAL__sub_I__ZN3Box3getEv FDE.  Those particular .L0s are defined in .text.test111111, which is why .text.test111111 is marked as needed.

Disabling .eh_frame marking is *not* the solution.  You should figure out why the assembler is generating relocs out-of-order and fix that.
Comment 2 Alan Modra 2021-12-20 14:48:02 UTC
Created attachment 13868 [details]
gas sorting of relocs

I poked at this a little more in my spare time.  Please check it out, and verify that I haven't made some silly mistake in the sort.
Comment 3 lifang_xia 2021-12-22 03:03:39 UTC
The gas of riscv will split frag in md_assemble if the insn could be relaxed in linker.tc-riscv.c:1372 (append_insn)
------------------------
/* We need to start a new frag after any instruction that can be
   optimized away or compressed by the linker during relaxation, to prevent
   the assembler from computing static offsets across such an instruction.
   This is necessary to get correct EH info.  */
if (reloc_type == BFD_RELOC_RISCV_CALL
    || reloc_type == BFD_RELOC_RISCV_CALL_PLT
    || reloc_type == BFD_RELOC_RISCV_HI20
    || reloc_type == BFD_RELOC_RISCV_PCREL_HI20
    || reloc_type == BFD_RELOC_RISCV_TPREL_HI20
    || reloc_type == BFD_RELOC_RISCV_TPREL_ADD)
  {
    frag_wane (frag_now);
    frag_new (0);
  }
--------------------------
The cfi_add_advance_loc will record the last symbol and current symbol, both of the symbols are temp symbol which named .L0. And both of them are in the section .test111111 which should be removed in linker. I name then to and from.

Because of the frag are splited, the to and from not the same frag.

Therefore, the gas will create a rs_cfa frag in .eh_frame for DW_CFA_advance_loc while dw2cfi.c(output_cfi_insn). The symbol to(.L0) and from(.L0) are in the section should be removed in linker.
--------------------------
static void
output_cfi_insn (struct cfi_insn_data *insn)
{
  offsetT offset;
  unsigned int regno;

  switch (insn->insn)
    {
    case DW_CFA_advance_loc:
      {
        symbolS *from = insn->u.ll.lab1;
        symbolS *to = insn->u.ll.lab2;

        if (symbol_get_frag (to) == symbol_get_frag (from))
          {
            ...
          }
        else
          {
            expressionS exp;

            exp.X_op = O_subtract;
            exp.X_add_symbol = to;
            exp.X_op_symbol = from;
            exp.X_add_number = 0;

            /* The code in ehopt.c expects that one byte of the encoding
               is already allocated to the frag.  This comes from the way
               that it scans the .eh_frame section looking first for the
               .byte DW_CFA_advance_loc4.  Call frag_grow with the sum of
               room needed by frag_more and frag_var to preallocate space
               ensuring that the DW_CFA_advance_loc4 is in the fixed part
               of the rs_cfa frag, so that the relax machinery can remove
               the advance_loc should it advance by zero.  */
            frag_grow (5);
            *frag_more (1) = DW_CFA_advance_loc4;

            frag_var (rs_cfa, 4, 0, DWARF2_LINE_MIN_INSN_LENGTH << 3,
                      make_expr_symbol (&exp), frag_now_fix () - 1,
                      (char *) frag_now);
          }
      }
      break; 
--------------------------------

The riscv backend will create a reloc named BFD_RELOC_RISCV_CFA in riscv_pre_output_hook. The reloc  depends on the symbol to(.L0) and from(.L0).

And riscv backend will convert BFD_RELOC_RISCV_CFA  to R_RISCV_SET6 and R_RISCV_SUB6 in md_apply_fix.

That's why .eh_frame depends on .text.test1111111
Comment 4 Nelson Chu 2021-12-27 10:41:37 UTC


(In reply to Alan Modra from comment #2)
> Created attachment 13868 [details]
> gas sorting of relocs
> 
> I poked at this a little more in my spare time.  Please check it out, and
> verify that I haven't made some silly mistake in the sort.

I can get the expected gc-section result after applying Alan's reorder patch, and the binary search sort looks correct, so looks good to me.  Thanks.
Comment 5 Nelson Chu 2021-12-27 11:46:40 UTC
(In reply to lifang_xia from comment #3)
> The riscv backend will create a reloc named BFD_RELOC_RISCV_CFA in
> riscv_pre_output_hook. The reloc  depends on the symbol to(.L0) and
> from(.L0).
> 
> And riscv backend will convert BFD_RELOC_RISCV_CFA to R_RISCV_SET6 and
> R_RISCV_SUB6 in md_apply_fix.

Each FDE is start from a R_RISCV_32_PCREL relocation, which is used to make sure the correctness of DW_CFA_advance_loc after relaxations.  I think ld assumes that the relocation are generated in the FDE r_offset order, so if we have any out of order relocation, then we may accidentally remark the FDE sections that we have decided to be discarded before.  According to your example, there is not only the .text.test111111 FDE needs BFD_RELOC_RISCV_CFA, the .text._Z41__static_initialization_and_destruction_0ii and .text._GLOBAL__sub_I__ZN3Box3getEv also need the BFD_RELOC_RISCV_CFA, so I figured this shouldn't be the root cause that let gc-section failed.  I think if we have any out of order relocation in the eh_frame, then we may also invalidate gc-sections.
Comment 6 Sourceware Commits 2021-12-28 12:34:20 UTC
The master branch has been updated by Alan Modra <amodra@sourceware.org>:

https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=443aa5f05edb58fc1774f926e9259b7c5a180926

commit 443aa5f05edb58fc1774f926e9259b7c5a180926
Author: Alan Modra <amodra@gmail.com>
Date:   Tue Dec 21 01:09:13 2021 +1030

    gas reloc sorting
    
    In some cases, eg. riscv_pre_output_hook, gas generates out-of-order
    relocations.  Various places in the linker assume relocs are sorted
    by increasing r_offset, which is normally the case.  Provide
    GAS_SORT_RELOCS to handle unsorted relocs.
    
    bfd/
            PR 28709
            * elf32-nds32.c (nds32_insertion_sort): Make static.
            * elf32-nds32.h (nds32_insertion_sort): Delete declaration.
    gas/
            PR 28709
            * write.c (write_relocs): Implement reloc sorting by r_offset
            when GAS_SORT_RELOCS.
            * config/tc-nds32.c (compar_relent, nds32_set_section_relocs): Delete.
            * config/tc-nds32.h (nds32_set_section_relocs): Don't declare.
            (SET_SECTION_RELOCS): Don't define.
            (GAS_SORT_RELOCS): Define.
            * config/tc-riscv.h (GAS_SORT_RELOCS): Define.
Comment 7 lifang_xia 2021-12-28 12:52:27 UTC
Thank you so much for explaining that.
Comment 8 Alan Modra 2022-01-13 08:40:43 UTC
Fixed