You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Consider applying -Wl,-z,relro or -Wl,-z,relro,-z,now by default #29877
These are some options that Debian applies, and that @gus wants to apply to all Rust code. Generally, Rust likes to harden things by default when there are no obvious disadvantages. Is it feasible that we might just use these by default?
Why not build with -no-plt instead? Seems a bit funny to erect the entire PLT apparatus to support lazy loading and then populate it at program startup.
Using a GOT instead will cause every function call to require a load of the address, even if the target is guaranteed to resolve in the same DSO. When emitting relocations for a PLT, the linker can replace PLT calls with direct relative calls in case of non-exported symbols AFAIK which is almost all calls in an average Rust program.
A PLT relocation for an external symbol is implemented as pc-relative call to a stub function which loads from the GOT. For an internal symbol this can trivially be relaxed to a pc-relative call to the actual function. The stub function is generated by the linker, not the compiler, so the linker has full control over it. For a GOT relocation however the load is part of the code emitted by the compiler and can't be replaced.
@bjorn3 internal symbols with "default" visibility are still interposable, and the compiler/linker are forced* to place a GOT entry for them - to account for the case that another binary preempts them.
*clang sometimes optimizes these calls anyway, but in doing so it knowingly breaks the ABI.
Rust uses -fno-semantic-interposition just like clang defaults to. Even if we didn't there wouldn't be any way to interpose mangled rust symbols without depending on implementation details. Symbol names are not stable and depend on the exact rustc version as well as whatever cargo passes as -Cmetadata argument. Only #[no_mangle] symbols can safely be interposed, and those are never internal anyway. Also how can internal symbols be interposed in the first place? They don't end up in .dynsym and as such the dynamic linker should know nothing about them. Rustc is very careful to avoid leaking any rust symbol from cdylibs and bins.
Rust uses -fno-semantic-interposition just like clang defaults to.
Actually this switch just avoids the interposition infrastructure for calls into the same translation unit.
Even if we didn't there wouldn't be any way to interpose mangled rust symbols without depending on implementation details. Symbol names are not stable and depend on the exact rustc version as well as whatever cargo passes as -Cmetadata argument. Only #[no_mangle] symbols can safely be interposed, and those are never internal anyway.
Indeed, the GOT is redundant for rust - and largely for C++ too. Yet this is not the point in question: The point is that PLT is redundant. The PLT isn't there to facilitate interposition, it is there to facilitate lazy binding. And this merge shows that rust (rightfully) avoids lazy binding anyway, so why pay the extra overhead?
Also how can internal symbols be interposed in the first place? They don't end up in .dynsym and as such the dynamic linker should know nothing about them.
You're right, internal symbols have hidden visibility and cannot be interposed. Again, this is a side discussion and not the point of my comment.
We don't yet know when compiling object files if non-local rust symbols will be looked up inside the same DSO or outside. Only once we start linking do we know this. This rules out using non-PLT PC-relative relocations for position independent executables. So PLT or GOT relocations are the only option. GOT relocations can't be relaxed to regular PC-relative calls, so PLT relocations it is.
Using a GOT instead will cause every function call to require a load of the address, even if the target is guaranteed to resolve in the same DSO. When emitting relocations for a PLT, the linker can replace PLT calls with direct relative calls in case of non-exported symbols AFAIK which is almost all calls in an average Rust program.
The -fno-plt case is handled through linker relaxation (works only for X86, with ld, gold and lld). More details are available in MaskRay's blog.
Relaxed case has small size overhead (1 byte for each call) but given that -fno-plt can result in significant savings (I got 10% for Clang with -DBUILD_SHARED_LIBS=ON few years ago, YMMV) this may be acceptable.
Why not build with -no-plt instead? Seems a bit funny to erect the entire PLT apparatus to support lazy loading and then populate it at program startup.
Turns out -fno-plt is enabled by default in Rust on Linux and Android now.
The -fno-plt case is handled through linker relaxation (works only for X86, with ld, gold and lld). More details are available in MaskRay's blog.
I can't find any details about the linker relaxation on that page.
However, if the target is non-preemptible, i.e. the target is resolved to the same component, this is actually a pessimization.
The linker (GNU ld, gold, and ld.lld) can mitigate the loss by rewriting the 6 bytes to addr32 call foo.
The downside is the one-byte-longer code sequence.
Just disabling the PLT wouldn't enable that relaxation, right? There would need to be some relocation applied to tell thet linker that the relaxation is allowed (the linker isn't allowed to do arbitrary rewrites. that can easily break code). I can't find any details about which relocation or under what exact conditions LLVM will emit them.
Activity
gus commentedon Nov 16, 2015
Sounds cool, but I didn't ask for that, @brson :)
Cheers,
Gus
brson commentedon Nov 17, 2015
Oops, wrong @gus. Sorry! Strong beard though.
gus commentedon Nov 17, 2015
;)
On Mon, Nov 16, 2015, 20:55 Brian Anderson notifications@github.com wrote:
genodeftest commentedon Dec 8, 2015
There is something similiar on Fedora: Change: Harden all packages
nagisa commentedon May 4, 2017
Downside is that not every alternative linker out there might support the flags.
brson commentedon May 4, 2017
@alexcrichton do recall if we've done anything here? I don't see 'relro' in the source.
alexcrichton commentedon May 4, 2017
No I don't believe we ever implemented this, but would be good to do so!
alexcrichton commentedon May 4, 2017
er didn't mean to close
cuviper commentedon May 4, 2017
FWIW, I also apply
-Clink-arg=-Wl,-z,relro,-z,nowin Fedora.4 remaining items
OfekShilon commentedon Sep 28, 2023
Why not build with
-no-pltinstead? Seems a bit funny to erect the entire PLT apparatus to support lazy loading and then populate it at program startup.bjorn3 commentedon Sep 28, 2023
Using a GOT instead will cause every function call to require a load of the address, even if the target is guaranteed to resolve in the same DSO. When emitting relocations for a PLT, the linker can replace PLT calls with direct relative calls in case of non-exported symbols AFAIK which is almost all calls in an average Rust program.
OfekShilon commentedon Sep 29, 2023
@bjorn3 I think PLT relocations must resolve to GOT slots anyway.
bjorn3 commentedon Sep 29, 2023
A PLT relocation for an external symbol is implemented as pc-relative call to a stub function which loads from the GOT. For an internal symbol this can trivially be relaxed to a pc-relative call to the actual function. The stub function is generated by the linker, not the compiler, so the linker has full control over it. For a GOT relocation however the load is part of the code emitted by the compiler and can't be replaced.
OfekShilon commentedon Sep 29, 2023
@bjorn3 internal symbols with "default" visibility are still interposable, and the compiler/linker are forced* to place a GOT entry for them - to account for the case that another binary preempts them.
*clang sometimes optimizes these calls anyway, but in doing so it knowingly breaks the ABI.
bjorn3 commentedon Sep 29, 2023
Rust uses
-fno-semantic-interpositionjust like clang defaults to. Even if we didn't there wouldn't be any way to interpose mangled rust symbols without depending on implementation details. Symbol names are not stable and depend on the exact rustc version as well as whatever cargo passes as-Cmetadataargument. Only#[no_mangle]symbols can safely be interposed, and those are never internal anyway. Also how can internal symbols be interposed in the first place? They don't end up in.dynsymand as such the dynamic linker should know nothing about them. Rustc is very careful to avoid leaking any rust symbol from cdylibs and bins.OfekShilon commentedon Sep 29, 2023
@bjorn3 This is an empirical matter. This toy test:
Seems to show that with or without PLT, in both gcc and clang, the GOT is used. Am I missing something?
OfekShilon commentedon Sep 29, 2023
Actually this switch just avoids the interposition infrastructure for calls into the same translation unit.
Indeed, the GOT is redundant for rust - and largely for C++ too. Yet this is not the point in question: The point is that PLT is redundant. The PLT isn't there to facilitate interposition, it is there to facilitate lazy binding. And this merge shows that rust (rightfully) avoids lazy binding anyway, so why pay the extra overhead?
You're right, internal symbols have hidden visibility and cannot be interposed. Again, this is a side discussion and not the point of my comment.
bjorn3 commentedon Sep 30, 2023
We don't yet know when compiling object files if non-local rust symbols will be looked up inside the same DSO or outside. Only once we start linking do we know this. This rules out using non-PLT PC-relative relocations for position independent executables. So PLT or GOT relocations are the only option. GOT relocations can't be relaxed to regular PC-relative calls, so PLT relocations it is.
yugr commentedon Jul 18, 2025
Are you sure? In this example
toolchain is able to generate direct call for both cases:
The
-fno-pltcase is handled through linker relaxation (works only for X86, withld,goldandlld). More details are available in MaskRay's blog.Relaxed case has small size overhead (1 byte for each call) but given that
-fno-pltcan result in significant savings (I got 10% for Clang with-DBUILD_SHARED_LIBS=ONfew years ago, YMMV) this may be acceptable.yugr commentedon Jul 19, 2025
Turns out
-fno-pltis enabled by default in Rust on Linux and Android now.bjorn3 commentedon Jul 25, 2025
I can't find any details about the linker relaxation on that page.
yugr commentedon Jul 25, 2025
bjorn3 commentedon Jul 25, 2025
Just disabling the PLT wouldn't enable that relaxation, right? There would need to be some relocation applied to tell thet linker that the relaxation is allowed (the linker isn't allowed to do arbitrary rewrites. that can easily break code). I can't find any details about which relocation or under what exact conditions LLVM will emit them.
yugr commentedon Jul 25, 2025
Yes, relaxations are done by linker based on relocations provided by compiler.
In this particular case under
-fno-pltcompiler will emit aR_X86_64_GOTPCRELXrelocation which linker will then use to relaxto
I think this is the relevant code in GNU ld.
Please note that our discussion is probly irrelevant by now because
plt_by_default=falsefor most amd64 linux platforms.