[lld] Handling multiple -init/-fini command line options

Hi,

The LLD linker in gnu flavor mode accepts multiple -init/-fini command
line options. For _all_ symbols specified by these options the linker
creates appropriate entries in the .init_array/.fini_array sections.
But it looks like LD and Gold linkers do not support this feature and
take in account only the last -init/-fini options.

% cat foo.c
int a = 0;
void foo() { a += 1; }
void bar() { a += 2; }

% cat main.c
extern int a;
int printf(const char *, ...);
int main() { printf("a: %d\n", a); }

% gcc -fPIC -shared -o libfoo.so -Wl,-init,foo -Wl,-init,bar foo.c
% gcc main.c -L. -lfoo -Wl,-rpath,.
% ./a.out
2

% gcc -fPIC -shared -o libfoo.so -Wl,-init,bar -Wl,-init,foo foo.c
% ./a.out
1

What is the reason of this incompatibility? The question is caused by
attempt to support DT_INIT/DT_FINI dynamic table tags. The table can
contain no more than one DT_INIT/DT_FINI tags. The LD and Gold linker
look up either default symbols (_init/_fini) or symbols specified by
the -init/-fini options and put their values to the tags.

I don’t know whether or not it’s intended, but it seems like a subtle but unnecessary incompatibility. I’d vote for making it compatible with GNU unless there’s a real reason to not do so.

This is a bug with the Gnu linker, IMO. There is also some behavioral difference for static executables when the -init/-fini options are used too.

The DT_INIT/DT_FINI correspond to one initializer function,where as DT_INIT_ARRAY/DT_FINI_ARRAY is used when there is more than one initalizer function/finalizer function respectively.

You could ask this same question in the binutils forums, but I think we should use DT_INIT_ARRAY/DT_FINI_ARRAY if more than one init/fini function is supplied by the user.

Shankar Easwaran

This is a bug with the Gnu linker, IMO. There is also some behavioral
difference for static executables when the -init/-fini options are used too.

The DT_INIT/DT_FINI correspond to one initializer function,where as
DT_INIT_ARRAY/DT_FINI_ARRAY is used when there is more than one initalizer
function/finalizer function respectively.

You could ask this same question in the binutils forums, but I think we
should use DT_INIT_ARRAY/DT_FINI_ARRAY if more than one init/fini function
is supplied by the user.

One reason why this behaviour is useful in the GNU world is if the
compiler adds an -init flag then the user can add
CFLAGS="-Wl,-init,foo" and override what the compiler passes without
causing an error. How useful this is in this particular case I am not
sure but it holds true for some linker options.

This is not true. The difference is that the DT_INIT / DT_FINI function
is responsible for calling into the .init / .fini block and for
DT_INIT_ARRAY / DT_FINI_ARRAY, the block is explicitly defined as a
list of pointers and it is the responsibility of either the CRT logic or
the dynamic linker to call them.

That said, I don't think more than one DT_INIT / DT_FINI entry should be
allowed as the latter entry will overwrite the earlier ones.

Joerg

Thanks Joerg, The snip of code that appears to run the init/fini was easily browseable with musl.

http://git.musl-libc.org/cgit/musl/tree/src/ldso/dynlink.c (function: do_init_fini).

That said, all of the code is essentially under NO_LEGACY_INITFINI, is the linker responsible to convert DT_INIT to DT_INITARRAY's ?

If so, I think more than one init/fini option should be converted to DT_INIT_ARRAY (or) DT_FINI_ARRAY respectively ?

Shankar Easwaran

By the way, gnu linker support both DT_INIT and DT_INIT_ARRAY tags at
the same time. For example, if input objects contain .init_array
section and _init symbol, gnu linker emits both DT_INIT and
DT_INIT_ARRAY tags. And I still do not see any reason to assign more
complicated semantic to the -init/-fini options and introduce
incompatibility with gnu linker.

If we want to be able to add functions to init_array using command line we can introduce new options like -init-array / -fini-array

The dynamic loader handles only one entry for DT_INIT. If there is more than one init option, we could convert this as an .init_array instead ?

If that doesnot work, we can come up with a .init_array option but I am not sure about how will you handle priority with init_array's ? Do you set linker defined init/fini symbols the last in priority ?

Shankar Easwaran

That said, all of the code is essentially under NO_LEGACY_INITFINI,
is the linker responsible to convert DT_INIT to DT_INITARRAY's ?

No. It can't do that. There are some non-trivial differences in the
semantics of the two.

If so, I think more than one init/fini option should be converted to
DT_INIT_ARRAY (or) DT_FINI_ARRAY respectively ?

No, the GNU behavior is correct here. Only one DT_INIT and one DT_FINI
should be written and the last option should win. It is like the dynamic
linker path in this regard.

Joerg

My idea:
1. -init/-fini options affects DT_INIT/DT_FINI tags only. If there is
no any option, the linker searches _init/_finit symbols and configure
DT_INIT/DT_FINI tags. This behaviour exactly corresponds to the GNU
linker. By the way symbols specified in the -init/-fini options can be
in any sections, not only .init/.fini.

2. -init-array/-fini-array options (I still not sure we need this
functionality) affect .init_array/.fini_array and
DT_INIT_ARRAY/DT_FINI_ARRAY tags. The order of these options define
priority.

The only problem is that we cannot setup priority of DT_INIT vs DT_INIT_ARRAY.

There is also an additional issue that needs to be handled,

The linker decides to convert .ctors/.dtors section to .init/.fini too in some circumstances.

There is also a case where we need to supported for static linking as well.

I looked into this a bit and there is a bug which illustrates ctors/dtors and init_array/fini_array

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=46770

Shankar Easwaran

I'm sorry, but I do not see how -init/-fini command line option
handling relates to this bug. Now the -init/-fini might cause creation
of .init_array/.fini_array sections. But in GNU linker AFAIK these
options affects DT_INIT/DT_FINI tags only.

A platform either uses .init/.fini sections or .init_array/.fini_array.
.ctors/.dtors is converted to whichever is used. It is unrelated to
handling of DT_INIT/DT_FINI.

Joerg

I don't know whether or not it's intended, but it seems like a subtle but
unnecessary incompatibility. I'd vote for making it compatible with GNU
unless there's a real reason to not do so.

I agree. We should do whatever GNU ld does here.

- Michael Spencer