This is an archive of the discontinued LLVM Phabricator instance.

[libc] Add init and fini array iteration to the loader.
ClosedPublic

Authored by sivachandra on Aug 2 2022, 12:20 AM.

Diff Detail

Event Timeline

sivachandra created this revision.Aug 2 2022, 12:20 AM
Herald added projects: Restricted Project, Restricted Project. · View Herald TranscriptAug 2 2022, 12:20 AM
sivachandra requested review of this revision.Aug 2 2022, 12:20 AM
abrachet added inline comments.
libc/test/integration/loader/linux/cxx_globals_test.cpp
17

__cxa_atexit is a alternative for the .fini_array, it doesn't use it

24

The compiler has no requirement that this constructor be placed in the .init_array. It seems the better way to test this would be to either use __attribute__(({destructor,constructor})) on a function or __attribute__((section(secname))) on a function pointer to explicitly put things in {preinit,init,fini}_array sections. The later being the only way I would know how to get something in .preinit_array

Address comments.

libc/test/integration/loader/linux/cxx_globals_test.cpp
17

I am not sure I understand this comment. My intention here was to say we cannot yet have a non-trivial destructor as it will lead to a call to __cxa_atexit which we do not yet have.

24

All good ideas so I have incorporated them. When you said that the "compiler has no requirement that this constructor be placed in the .init_array", I suppose you mean that the object initialization can be inlined, eliminating the need for an explicit constructor call (may be at -O3)? I have expanded the constructor a little bit to reduce the chance for such a possibility. Also, when we include a non-trivial destructor, an .init_array entry will be added anyway so I have left the test below as is.

If libc would support Lit tests, then you could:

  1. compile the test binary exactly as you like it
  2. inspect the binary with llvm-objdump
  3. run the binary and observe the execution as you see fit

it would give you higher confidence than forcing loader tests into unit tests.

lntue accepted this revision.Aug 3 2022, 10:31 AM
This revision is now accepted and ready to land.Aug 3 2022, 10:31 AM
lntue retitled this revision from [libc] Add init and fini array iteration to the laoder. to [libc] Add init and fini array iteration to the loader..Aug 3 2022, 10:40 AM

If libc would support Lit tests, then you could:

  1. compile the test binary exactly as you like it
  2. inspect the binary with llvm-objdump
  3. run the binary and observe the execution as you see fit

it would give you higher confidence than forcing loader tests into unit tests.

In general, we are taking small steps (sometimes incomplete) to understand the various infrastructure needs. May be one day we will start using lit-tests. But, unit tests are still very nice :)

If libc would support Lit tests, then you could:

  1. compile the test binary exactly as you like it
  2. inspect the binary with llvm-objdump
  3. run the binary and observe the execution as you see fit

it would give you higher confidence than forcing loader tests into unit tests.

In general, we are taking small steps (sometimes incomplete) to understand the various infrastructure needs. May be one day we will start using lit-tests. But, unit tests are still very nice :)

No worries.

Got these comments in a little late, oops

libc/loader/linux/x86_64/start.cpp
93

It's not clear to me why these take argc, argv and env. .init_array functions wouldn't take these because in the general they would only be meaningful when loading an executable and not a shared object. I suppose it isn't harmful to pass arguments to a function which doesn't take them, though it seems surprising given likely no constructor functions in the wild take these.

118

These should happen in reverse order.

libc/test/integration/loader/linux/cxx_globals_test.cpp
17

I see sorry, I thought it was specific to .fini_array

libc/test/integration/loader/linux/init_fini_array_test.cpp
39 ↗(On Diff #449395)

If the integration tests check that the process exited normally you could return 1 from main and _Exit(0) here to ensure this is called. Additionally it might make sense to specify a priority to destructor and constructor, like destructor(num) to ensure they are called in the right order.

sivachandra added inline comments.Aug 3 2022, 1:59 PM
libc/loader/linux/x86_64/start.cpp
93

Neither do I know of any practical examples. Most libcs do not pass any args to the init functions. However, this patch matches glibc behavior.

$ cat init.cpp
#include <stdio.h>

__attribute__((constructor)) void initfunc(int argc, char **argv, char **envp) {
  printf("argc: %d\n", argc);
  for (int i = 0; i < argc; ++i) {
    printf("\targ[%d] = %s\n", i, argv[i]);
  }
}

int main() {
  return 0;
}
$ clang++ init.cpp
$ ./a.out
argc: 1
	arg[0] = ./a.out
$ ./a.out abc def ghi
argc: 4
	arg[0] = ./a.out
	arg[1] = abc
	arg[2] = def
	arg[3] = ghi
118

Oops, yes! I will fix it in the next pass. I am working on the __cxa_atexit hook up anyway so will fix it in that pass.

libc/test/integration/loader/linux/init_fini_array_test.cpp
39 ↗(On Diff #449395)

OK - I will add more tests with destructor priority which I think would be better than what we have. Priorities + the _Exit(0) idea together will make it a complete test I think.