[PATCH v7] elf: Add ELF_DYNAMIC_AFTER_RELOC to rewrite PLT
H.J. Lu
hjl.tools@gmail.com
Wed Jan 17 16:54:07 GMT 2024
On Wed, Jan 17, 2024 at 12:56 AM Fangrui Song <maskray@google.com> wrote:
>
> On 2024-01-11, H.J. Lu wrote:
> >On Thu, Jan 11, 2024 at 6:44 AM Cristian Rodríguez
> ><cristian@rodriguez.im> wrote:
> >>
> >>
> >>
> >> On Thu, Jan 11, 2024 at 11:33 AM Sam James <sam@gentoo.org> wrote:
> >>>
> >>>
> >>>
> >>> systemd also enables this for its own services too, etc.
> >>
> >>
> >> Yes, I wonder why I do not get any service failure since I enabled this tunable globally on my system..by using DefaultEnvironment= in system.conf
> >
> >A patch was sent to
> >
> >https://patchwork.sourceware.org/project/glibc/patch/20240111155849.8976-1-hjl.tools@gmail.com/
> >
> >It fixed sound on Fedora 39.
> >
> >--
> >H.J.
>
> I wonder whether this delicate micro-optimization (with many constraints) might lead to the loss of file-backed transparent huge pages.
> (I am suspecting, but I know very little about huge pages, so ...)
>
> +Matthew Wilcox
>
> I've jotted down some notes (https://maskray.me/blog/2023-02-19-linker-notes-on-x86#mark-plt). Copying them here:
>
> ---
>
> In 2023-09, GNU ld [introduced `-z mark-plt`](https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=832ca732b8a96ff9a3e7c4abf24098bf2a59a96d) to communicate information to rtld to rewrite eligible indirect jump instructions to direct jump instructions.
>
> * The linker adds dynamic tags `DT_X86_64_PLT/DT_X86_64_PLTSZ/DT_X86_64_PLTENT`.
> * The addend in `R_X86_64_JUMP_SLOT` relocations are adjusted to indicate the offset of the indirect jump instruction.
>
> Since 2024-01, if binutils is configured with `--enable-mark-plt`, `-z mark-plt` will be the default.
>
> In glibc, when `GLIBC_TUNABLES=glibc.cpu.plt_rewrite=1` or 2 is specified, lazy PLT binding is disabled, and an object file enables `DT_X86_64_PLT/DT_X86_64_PLTSZ/DT_X86_64_PLTENT` tags, glibc [rewrites eligible PLT entries](https://sourceware.org/git/?p=glibc.git;a=commit;h=848746e88ec2aa22e8dea25f2110e2b2c59c712e).
> After relocating an object file, `x86_64_dynamic_after_reloc` (due to the `ELF_DYNAMIC_AFTER_RELOC` hook) calls `x86_64_rewrite_plt_in_place`, which changes the permission of the `.plt` memory page (`MAP_PRIVATE`) to `PROT_READ|PROT_WRITE`, rewrites eligible PLT entries, then changes the page to `PROT_READ|PROT_EXEC`.
>
> For each `R_X86_64_JUMP_SLOT` relocation, `x86_64_rewrite_plt_in_place` reads the target address from the `.got.plt` entry, computes the indirect jump address using the addend, then checks whether the jump target is reachable with a direct JMP instruction.
> If so, the indirect jump instruction is rewritten to `jmp $target`; otherwise, when `GLIBC_TUNABLES=glibc.cpu.plt_rewrite=2` is specified on APX processors, the indirect jump instruction is rewritten to `jmpabs $target` (64-bit absolute jump).
>
> The mprotect operations increase private data uses and are incompatible with [memory-deny-write-execute](https://git.kernel.org/linus/b507808ebce23561d4ff8c2aa1fb949fe402bc61).
This also applies to JITs.
> One primary cost of PLT is the use of an extra instruction cache line. Rewriting jump instruction does not eliminate this overhead.
The indirect branch in PLT may cause branch misprediction in some cases.
> Since the main executable is far away from shared objects in the address space, in the absence of APX `jmpabs`, the PLT rewriting will very likely not occur.
On my machine, PIE can be placed with 2GB of libc.so:
[hjl@gnu-tgl-2 build-x86_64-linux]$ file ./stdlib/tst-swapcontext1
./stdlib/tst-swapcontext1: ELF 64-bit LSB pie executable, x86-64,
version 1 (SYSV), dynamically linked, interpreter
/export/build/gnu/tools-build/glibc-cet/build-x86_64-linux/elf/ld.so,
BuildID[sha1]=f70596fed608bbb8644cfe3f1d5b72cdff3e99ac, for GNU/Linux
3.2.0, with debug_info, not stripped
[hjl@gnu-tgl-2 build-x86_64-linux]$
GLIBC_TUNABLES=glibc.cpu.plt_rewrite=1 LD_DEBUG=files:bindings
LD_BIND_NOW=1 ./stdlib/tst-swapcontext1 > log 2>&1
[hjl@gnu-tgl-2 build-x86_64-linux]$ grep "direct " log
3965: changing PLT in
'/export/build/gnu/tools-build/glibc-cet/build-x86_64-linux/libc.so.6'
to direct branch
3965: changing 'realloc' PLT entry in
'/export/build/gnu/tools-build/glibc-cet/build-x86_64-linux/libc.so.6'
to direct branch
3965: changing 'calloc' PLT entry in
'/export/build/gnu/tools-build/glibc-cet/build-x86_64-linux/libc.so.6'
to direct branch
3965: changing PLT in './stdlib/tst-swapcontext1' to direct branch
3965: changing 'getenv' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing '__isoc23_strtoul' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'raise' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'free' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'putchar' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing '__errno_location' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing '_exit' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'puts' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'clock_gettime' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'setenv' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'write' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'getpid' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'fclose' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'strlen' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'chdir' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing '__stack_chk_fail' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'mmap' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'getopt_long' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'printf' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'swapcontext' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'nanosleep' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'gmtime_r' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'setrlimit64' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing '__assert_fail' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'alarm' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'signal' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'fprintf' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'fopen64' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'memcpy' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'makecontext' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'kill' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing '__isoc23_strtol' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'malloc' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'vprintf' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'strsignal' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'setvbuf' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'waitpid' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'fopen' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'perror' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing '__cxa_atexit' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'setpgid' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'sprintf' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'exit' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'fwrite' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'fstat64' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'getcontext' PLT entry in
'./stdlib/tst-swapcontext1' to direct branch
3965: changing 'execv' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'fork' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
3965: changing 'usleep' PLT entry in './stdlib/tst-swapcontext1'
to direct branch
[hjl@gnu-tgl-2 build-x86_64-linux]$
> PowerPC32 has a BSS-PLT ABI that generates PLT entries on the fly, which shares some similarity. BSS-PLT is obsoleted primarily due to security concerns.
>
> As a minor note, this optimization will [nullify](https://groups.google.com/g/x86-64-abi/c/vbuHVMK_RIA/m/zi0qi_0pBQAJ) the [`.plt.got` optimization (little benefit, but clever)](/blog/2021-08-29-all-about-global-offset-table#combining-.got-and-.got.plt).
>
> -fno-plt may also do better than this PLT optimization.
That is true for most cases. But in some cases, direct call + direct
jump is faster than indirect call.
This feature has a minimal performance impact for most people. But
it is very useful for cases where PLT performance is critical.
--
H.J.
More information about the Libc-alpha
mailing list