[PATCH] Linux/x86: Enable --rosegment by default

Fangrui Song i@maskray.me
Thu Mar 19 03:16:28 GMT 2026


On Wed, Mar 18, 2026 at 7:56 PM hev <r@hev.cc> wrote:
>
> On Thu, Mar 19, 2026 at 10:06 AM Fangrui Song <i@maskray.me> wrote:
> > For x86, --no-rosegment -z noseparate-code -z max-page-size=2097152 is
> > the most compact layout that works with transparent hugepages.
> > Google and ChromeOS have been using such a layout for a long time.
>
> --no-rosegment -z noseparate-code -z max-page-size=2097152 is somewhat
> more compact than --no-rosegment -z separate-code -z
> max-page-size=2097152. However, due to alignment, there is still a
> sizable gap between the RE and RW segments, which significantly
> increases the file size (e.g. hello world: 6K -> 2.1M). This is almost
> impractical with 32M huge pages.
>
>   LOAD           0x0000000000000000 0x0000000000000000
> 0x0000000000000000
>                  0x0000000000000738 0x0000000000000738  R E
> 0x200000
>   LOAD           0x00000000001ffe10 0x00000000003ffe10 0x00000000003ffe10
>                  0x0000000000000200 0x0000000000000208  RW     0x200000

This is due to the suboptimal PT_GNU_RELRO layout in GNU ld. See
https://sourceware.org/bugzilla/show_bug.cgi?id=30612
mold and ld.lld avoid this issue by introducing the .relro_padding
section. lld's implementation also emulates
DATA_SEGMENT_ALIGN/DATA_RELRO_END.

https://maskray.me/blog/2020-11-15-explain-gnu-linker-options#z-relro

"""
GNU ld uses one RW PT_LOAD program header with padding at the start.
The first half of the PT_LOAD overlaps with PT_GNU_RELRO. The padding
is added so that the end of PT_GNU_RELRO is aligned by max-page-size.
(See ld.bfd --verbose output.) Prior to GNU ld 2.39, the end was
aligned by common-page-size. GNU ld's one RW PT_LOAD layout makes the
alignment increase the file size. max-page-size can be large, such as
65536 for many systems, causing wasted space.

lld utilitizes two RW PT_LOAD program headers: one for RELRO sections
and the other for non-RELRO sections. Although this might appear
unusual initially, it eliminates the need for alignment padding as
seen in GNU ld's layout. Key changes:

https://reviews.llvm.org/D58892 switched from
PT_LOAD(PT_GNU_RELRO(.data.rel.ro .bss.rel.ro) .data .bss) to
PT_LOAD(PT_GNU_RELRO(.data.rel.ro .bss.rel.ro)) PT_LOAD(.data. .bss).
The end of the PT_GNU_RELRO segment and the associated RW PT_LOAD
segment is padded to a common-page-size boundary. The padding section
.relro_padding is like mold. Before LLD 18, there is an issue that
runtime_page_size < common-page-size does not work.

The layout used by mold is similar to that of lld. In mold's case, the
end of PT_GNU_RELRO is padded to max-page-size by appending a
SHT_NOBITS .relro_padding section. This approach ensures that the last
page of PT_GNU_RELRO is protected, regardless of the system page size.
However, when the system page size is less than max-page-size, the map
from the first RW PT_LOAD is larger than needed.
"""

> > If you use --rosegment to separate R and RX segments, there is a trade-off
> >
> > * With -z separate-code , THP works well for the code segment.  The
> > cost is massive file size inflation due to 2MiB padding between the R
> > and R+X segments.
> > * With -z noseparate-code, the RX segment starts at a non-aligned
> > address. Last time I checked, Linux kernel's THP requires both the
> > file offset and the VMA start to be huge-page-aligned, so the code
> > segment cannot use THP.
>
> It doesn’t seem to be the case anymore. With -z noseparate-code, the
> RX segment now starts at offset 0, which works for alignment with any
> huge page size. That’s also why I suggested putting the RX segment
> first.
>
>   LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
>                  0x0000000000000738 0x0000000000000738  R E    0x1000
>   LOAD           0x0000000000000e10 0x0000000000001e10 0x0000000000001e10
>                  0x0000000000000200 0x0000000000000208  RW     0x1000

I was describing lld's -z noseparate-code --rosegment behavior.
With GNU ld's -z noseparate-code, --rosegment and --no-rosegment don't
make a difference.

Actually, GNU ld's --rosegment behavior seems broken...

  ┌───────────────────────────────────┬──────────┬────────────────┐
  │              Options              │ Segments │     Layout     │
  ├───────────────────────────────────┼──────────┼────────────────┤
  │ -z noseparate-code --no-rosegment │ 2        │ R+X, RW        │
  ├───────────────────────────────────┼──────────┼────────────────┤
  │ -z noseparate-code --rosegment    │ 2        │ R+X, RW (same) │
  ├───────────────────────────────────┼──────────┼────────────────┤
  │ -z separate-code --no-rosegment   │ 4        │ R, R+X, R, RW  │
  ├───────────────────────────────────┼──────────┼────────────────┤
  │ -z separate-code --rosegment      │ 3        │ R+X, R, RW     │
  └───────────────────────────────────┴──────────┴────────────────┘

I didn't expect -z separate-code --no-rosegment to have more segments
than -z separate-code --rosegment.

> > "In -z noseparate-code layouts, the file content starts somewhere at
> > the first page, potentially wasting half a huge page on unrelated
> > content." However, the file size saving advantage outweighs this THP
> > concern.
> > (https://maskray.me/blog/2023-12-17-exploring-the-section-layout-in-linker-output)
> >
> >
> > Gadget avoidance has always been more security theater than substance.
>
> Agreed.
>
> Thanks,
> Rui


More information about the Binutils mailing list