Skip to content

[compiler-rt].eh_frame mapped writable in Clang builds (due to __EH_FRAME_LIST__), unlike GCC #155764

@dongjianqiang2

Description

@dongjianqiang2

When compiling a simple program with Clang, the .eh_frame section is placed into a writable segment because of the way __EH_FRAME_LIST__ is defined.
GCC, in contrast, places .eh_frame into a read-only segment.

Although GNU_RELRO later makes the mapping read-only, the initial load maps .eh_frame as writable, which increases virtual memory usage and differs from GCC’s behavior.

gcc main.c -o a.out
readelf -l a.out

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000001c0 0x00000000000001c0  R      0x8
  INTERP         0x0000000000000200 0x0000000000000200 0x0000000000000200
                 0x000000000000001d 0x000000000000001d  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-aarch64.so.1]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x00000000000008a0 0x00000000000008a0  R E    0x10000
  LOAD           0x0000000000000db8 0x0000000000010db8 0x0000000000010db8
                 0x0000000000000280 0x0000000000000288  RW     0x10000
  DYNAMIC        0x0000000000000dc8 0x0000000000010dc8 0x0000000000010dc8
                 0x00000000000001f0 0x00000000000001f0  RW     0x8
  GNU_EH_FRAME   0x00000000000007ac 0x00000000000007ac 0x00000000000007ac
                 0x000000000000003c 0x000000000000003c  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000000db8 0x0000000000010db8 0x0000000000010db8
                 0x0000000000000248 0x0000000000000248  R      0x1

 Section to Segment mapping:
  Segment Sections...
   00
   01     .interp
   02     .interp .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .note.ABI-tag .eh_frame_hdr .eh_frame
   03     .init_array .fini_array .dynamic .got .got.plt .data .bss
   04     .dynamic
   05     .eh_frame_hdr
   06
   07     .init_array .fini_array .dynamic .got

.eh_frame stays in a read-only PT_LOAD segment, consistent with unwind data being immutable.

clang main.c -fuse-ld=ld -o a.out
readelf -l a.out

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000001f8 0x00000000000001f8  R      0x8
  INTERP         0x0000000000000238 0x0000000000000238 0x0000000000000238
                 0x000000000000001d 0x000000000000001d  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-aarch64.so.1]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000834 0x0000000000000834  R E    0x10000
  LOAD           0x0000000000000cf8 0x0000000000010cf8 0x0000000000010cf8
                 0x0000000000000350 0x00000000000003a0  RW     0x10000
  DYNAMIC        0x0000000000000dc8 0x0000000000010dc8 0x0000000000010dc8
                 0x00000000000001f0 0x00000000000001f0  RW     0x8
  NOTE           0x0000000000000258 0x0000000000000258 0x0000000000000258
                 0x0000000000000020 0x0000000000000020  R      0x4
  GNU_EH_FRAME   0x0000000000000808 0x0000000000000808 0x0000000000000808
                 0x000000000000002c 0x000000000000002c  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000000cf8 0x0000000000010cf8 0x0000000000010cf8
                 0x0000000000000308 0x0000000000000308  R      0x1

 Section to Segment mapping:
  Segment Sections...
   00
   01     .interp
   02     .interp .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr
   03     .eh_frame .init_array .fini_array .dynamic .got .got.plt .data .bss
   04     .dynamic
   05     .note.ABI-tag
   06     .eh_frame_hdr
   07
   08     .eh_frame .init_array .fini_array .dynamic .got

.eh_frame is in a read-write PT_LOAD segment (later covered by GNU_RELRO).

which causes .eh_frame pages to be mapped writable at load time.
Virtual memory usage is higher since those writable mappings cannot be immediately reclaimed after RELRO.
Memory-sensitive systems (containers, embedded devices) may see unnecessary overhead.

GCC uses __EH_FRAME_BEGIN__ with const attributes, so .eh_frame is read-only.
Clang defines __EH_FRAME_LIST__ as a writable array inside .eh_frame, which forces .eh_frame into a writable segment.

Possible fixes:
Declare __EH_FRAME_LIST__ as static void * const [] so the symbol is treated as read-only and .eh_frame does not force a RW PT_LOAD.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions