Hello, On Thu, 7 Apr 2022, Aaron Puchert wrote:
Am 05.04.22 um 20:24 schrieb Cristian Rodríguez:
On Tue, Apr 5, 2022 at 2:17 PM Cristian Rodríguez <cristian@rodriguez.im> wrote:
You should also note that you will not be able to interpose symbols anymore, you need to relink everything again to do so.
That's not true. The only real effect that -znow has is to set the DT_BIND_NOW flag in the produced component (shared lib or executable). And that simply makes ld.so resolve all relocations from that component to be resolved at load time, instead of lazily. -znow does _not_ change the ELF interposition and visibility rules in any way. Just because it came up in the thread: do note that the PLT is not for enabling interposition, it's for enabling lazy binding. Interposition is also possible without a PLT, as it always was for e.g. data symbols. So, it doesn't matter (for interposition) if the PLT is now used or not. With -znow it would be possible to avoid the PLT by essentially inlining it: callit(); would be translated roughly to callq *GOT[id_of_callit] (instead of 'callq plt_slot_of_callit'), and at that GOT slot a normal relocation to symbol 'callit' would be recorded. And that one is (with znow) resolved at load time, instead of by the PLT slot calling into the dynamic linker. That scheme can break certain assumptions (e.g. from LD_AUDIT), so it's not done. But even if it were done it wouldn't break interposition. Now, there are contrived examples where znow changes something. You basically need to construct examples where the final outcome of symbol resolution depends on timing: i.e. if the resolved symbol changes from load time compared to somewhen later. One could imagine a component calling foobar() (which may have a definition in say libfoo), and then a dlopen'ed component is also defining foobar. Now with lazy loading foobar would be only resolved at call time, and hence when that occurs (for the first time) after the dlopen, _and_ that dlopen is done in a very specific way so that that component comes in front of libfoo (which isn't normally the case) then foobar will be resolved to the dlopen-component instead of libfoo. With znow it will always resolve to the definition in libfoo (because the dlopen hadn't happened yet).
Before somebody asks why it is because LD does PLT elision at build time.. it changes PLT->GOT when -z now since the whole reason for the existence of PLT is lazy binding.
Can you elaborate on this? The GOT is still filled out by the dynamic linker, so if the linker thinks a symbol should be interposed (and the logic behind interposition is architecture-independent of course), why can it not write the address of a different function into the GOT?
And this is exactly what would happen if the PLT would be elided^Winlined and hence also not break interposition. As you note the PLT is still used on x86-64 though, for other reasons. To wit: % cat app.c extern void interpose_this(void); int main() { interpose_this(); return 0; } % cat lib.c void interpose_this(void) { } % cat interpose.c extern int printf(const char *, ...); void interpose_this(void) { printf("it works\n"); } % gcc -Wl,-z,now -fPIC -shared -o interpose.so interpose.c % gcc -Wl,-z,now -fPIC -shared -o liblib.so lib.c % gcc -Wl,-z,now -Wl,-rpath,. app.c -L. -llib % ./a.out % LD_PRELOAD=interpose.so ./a.out it works So, it works, like it says there ;-) Ciao, Michael.