[PATCH] ld: Disable separate code by default for Linux/x86
H.J. Lu
hjl.tools@gmail.com
Mon Mar 16 21:47:13 GMT 2026
On Mon, Mar 16, 2026 at 10:03 AM hev <r@hev.cc> wrote:
>
> On Tue, Mar 17, 2026 at 12:35 AM H.J. Lu <hjl.tools@gmail.com> wrote:
> >
> > On Mon, Mar 16, 2026 at 9:21 AM hev <r@hev.cc> wrote:
> > >
> > > On Mon, Mar 16, 2026 at 11:58 PM H.J. Lu <hjl.tools@gmail.com> wrote:
> > > >
> > > > On Mon, Mar 16, 2026 at 7:20 AM WANG Rui <r@hev.cc> wrote:
> > > > >
> > > > > Years ago, commit f6aec96dce1d ("ld: Add --enable-separate-code") introduced
> > > > > -z separate-code, mainly to avoid mixing code and data in the same segment
> > > > > for better cache performance, and enabled it by default for Linux/x86.
> > > >
> > > > -z separate-code reduces cache pollution and improves security by avoiding
> > > > putting read-only data in the same page with code.
> > >
> > > I see. IIUC, the security aspect you mentioned mainly concerns
> > > avoiding an executable ELF header. In that case we probably need to
> > > make a trade-off here.
> > >
> > > > > Today, Linux already has working support for mapping executable code with THP,
> > > > > which can noticeably reduce iTLB pressure and bring measurable performance
> > > > > benefits.
> > > > >
> > > > > In practice, I observed that on AArch64 and LoongArch64 distros, ELF executables
> > > > > typically place the code PT_LOAD segment first, so both the virtual address and
> > > > > file offset are 0. That turns out to be an important prerequisite for THP-aware
> > > >
> > > > Should ld.so map read-only data segment plus code segment into the same huge
> > > > page? This should have the similar effect as -z noseparate-code.
> > >
> > > A huge page has a single set of permissions, so it doesn't seem
> > > possible for part of the page to be read-only and non-executable while
> > > another part is executable.
> >
> > With -z separate-code, I got
> >
> > Program Headers:
> > Type Offset VirtAddr PhysAddr
> > FileSiz MemSiz Flg Align
> > PHDR 0x000040 0x0000000000000040 0x0000000000000040
> > 0x0002d8 0x0002d8 R 0x8
> > INTERP 0x001000 0x0000000000001000 0x0000000000001000
> > 0x00001c 0x00001c R 0x1
> > [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
> > LOAD 0x000000 0x0000000000000000 0x0000000000000000
> > 0x0004a1 0x0004a1 R E 0x1000
> > LOAD 0x001000 0x0000000000001000 0x0000000000001000
> > 0x0003f0 0x0003f0 R 0x1000
> > LOAD 0x001d98 0x0000000000002d98 0x0000000000002d98
> > 0x000274 0x000278 RW 0x1000
>
> Huh? Why is the layout you get different from mine? With -z
> separate-code, the read-only segment should normally come first, so
> that the ELF header and program headers fall into that segment and
> remain read-only and non-executable for security reasons.
It is because my binutils was configured --enable-rosegment.
> With -z separate-code, I got
>
> Program Headers:
> Type Offset VirtAddr PhysAddr
> FileSiz MemSiz Flags Align
> PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
> 0x0000000000000310 0x0000000000000310 R 0x8
> INTERP 0x0000000000000374 0x0000000000000374 0x0000000000000374
> 0x000000000000001c 0x000000000000001c R 0x1
> [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
> LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
> 0x00000000000005c8 0x00000000000005c8 R 0x1000
> LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000
> 0x0000000000000139 0x0000000000000139 R E 0x1000
> LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000
> 0x00000000000000c8 0x00000000000000c8 R 0x1000
> LOAD 0x0000000000002e10 0x0000000000003e10 0x0000000000003e10
> 0x0000000000000200 0x0000000000000208 RW 0x1000
>
> With -z noseparate-code, I got
>
> Program Headers:
> Type Offset VirtAddr PhysAddr
> FileSiz MemSiz Flags Align
> PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
> 0x00000000000002a0 0x00000000000002a0 R 0x8
> INTERP 0x0000000000000304 0x0000000000000304 0x0000000000000304
> 0x000000000000001c 0x000000000000001c R 0x1
> [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
> LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
> 0x0000000000000768 0x0000000000000768 R E 0x1000
> LOAD 0x0000000000000e10 0x0000000000001e10 0x0000000000001e10
> 0x0000000000000200 0x0000000000000208 RW 0x1000
>
> >
> > With -z noseparate-code, I got
> >
> > Program Headers:
> > Type Offset VirtAddr PhysAddr
> > FileSiz MemSiz Flg Align
> > PHDR 0x000040 0x0000000000000040 0x0000000000000040
> > 0x0002a0 0x0002a0 R 0x8
> > INTERP 0x000304 0x0000000000000304 0x0000000000000304
> > 0x00001c 0x00001c R 0x1
> > [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
> > LOAD 0x000000 0x0000000000000000 0x0000000000000000
> > 0x000858 0x000858 R E 0x1000
> > LOAD 0x000d98 0x0000000000001d98 0x0000000000001d98
> > 0x000274 0x000278 RW 0x1000
> >
> > Please remind me of the benefits of -z noseparate-code. If ld.so maps
> >
> > LOAD 0x000000 0x0000000000000000 0x0000000000000000
> > 0x0004a1 0x0004a1 R E 0x1000
> > LOAD 0x001000 0x0000000000001000 0x0000000000001000
> > 0x0003f0 0x0003f0 R 0x1000
> >
> > in the same huge page with RW permission, will we get the same benefits?
>
> One benefit of -z noseparate-code is that the executable PT_LOAD ends
> up at virtual address 0 with file offset 0. For huge page mappings,
> both the virtual address and the file offset need to be aligned to the
> huge page size, e.g.
>
> ((p_vaddr | p_offset) & (HUGE_PAGE_SIZE - 1)) == 0
>
> Are you suggesting that, with the -z separate-code layout, ld.so would
> ignore the non-executable flag of the first PT_LOAD and merge it with
> the second PT_LOAD, mapping them together as read-only executable?
> That might help with existing binaries, but wouldn't it blur the
> permission boundaries between segments?
>
That is correct. It should have the similar effect as -z noseparate-code.
The difference is link-time vs run-time. Of course, ld.so should do it only
when huge page is in use.
--
H.J.
More information about the Libc-alpha
mailing list