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

Reviewed By: lntue

Differential Revision: https://reviews.llvm.org/D130966

GitOrigin-RevId: f23076f6c5668aa8535da6b80b36f6bdc41b44b2
diff --git a/loader/linux/x86_64/start.cpp b/loader/linux/x86_64/start.cpp
index 0338206..9ed2bf7 100644
--- a/loader/linux/x86_64/start.cpp
+++ b/loader/linux/x86_64/start.cpp
@@ -90,6 +90,35 @@
                                                                       : true;
 }
 
+using InitCallback = void(int, char **, char **);
+using FiniCallback = void(void);
+
+extern "C" {
+// These arrays are present in the .init_array and .fini_array sections.
+// The symbols are inserted by linker when it sees references to them.
+extern uintptr_t __preinit_array_start[];
+extern uintptr_t __preinit_array_end[];
+extern uintptr_t __init_array_start[];
+extern uintptr_t __init_array_end[];
+extern uintptr_t __fini_array_start[];
+extern uintptr_t __fini_array_end[];
+}
+
+static void call_init_array_callbacks(int argc, char **argv, char **env) {
+  size_t preinit_array_size = __preinit_array_end - __preinit_array_start;
+  for (size_t i = 0; i < preinit_array_size; ++i)
+    reinterpret_cast<InitCallback *>(__preinit_array_start[i])(argc, argv, env);
+  size_t init_array_size = __init_array_end - __init_array_start;
+  for (size_t i = 0; i < init_array_size; ++i)
+    reinterpret_cast<InitCallback *>(__init_array_start[i])(argc, argv, env);
+}
+
+static void call_fini_array_callbacks() {
+  size_t fini_array_size = __fini_array_end - __fini_array_start;
+  for (size_t i = 0; i < fini_array_size; ++i)
+    reinterpret_cast<FiniCallback *>(__fini_array_start[i])();
+}
+
 } // namespace __llvm_libc
 
 using __llvm_libc::app;
@@ -175,8 +204,15 @@
 
   __llvm_libc::self.attrib = &__llvm_libc::main_thread_attrib;
 
+  __llvm_libc::call_init_array_callbacks(
+      app.args->argc, reinterpret_cast<char **>(app.args->argv),
+      reinterpret_cast<char **>(env_ptr));
+
   int retval = main(app.args->argc, reinterpret_cast<char **>(app.args->argv),
                     reinterpret_cast<char **>(env_ptr));
+
+  __llvm_libc::call_fini_array_callbacks();
+
   __llvm_libc::cleanup_tls(tls.addr, tls.size);
   __llvm_libc::syscall(SYS_exit, retval);
 }
diff --git a/test/integration/loader/linux/CMakeLists.txt b/test/integration/loader/linux/CMakeLists.txt
index 137b46a..5138f7a 100644
--- a/test/integration/loader/linux/CMakeLists.txt
+++ b/test/integration/loader/linux/CMakeLists.txt
@@ -51,3 +51,12 @@
     libc.src.errno.errno
     libc.src.sys.mman.mmap
 )
+
+add_integration_test(
+  init_fini_array_test
+  SUITE libc-loader-tests
+  LOADER
+    libc.loader.linux.crt1
+  SRCS
+    init_fini_array_test.cpp
+)
diff --git a/test/integration/loader/linux/init_fini_array_test.cpp b/test/integration/loader/linux/init_fini_array_test.cpp
new file mode 100644
index 0000000..a3f0402
--- /dev/null
+++ b/test/integration/loader/linux/init_fini_array_test.cpp
@@ -0,0 +1,54 @@
+//===-- Loader test to test init and fini array iteration -----------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "utils/IntegrationTest/test.h"
+
+class A {
+private:
+  int val[1024];
+
+public:
+  A(int i, int a) {
+    for (int k = 0; k < 1024; ++k)
+      val[k] = 0;
+    val[i] = a;
+  }
+
+  // TODO: When we have implementation for __cxa_atexit, an explicit definition
+  // of the destructor should be provided to test that path of registering the
+  // destructor callback for a global.
+  ~A() = default;
+
+  int get(int i) const { return val[i]; }
+};
+
+int GLOBAL_INDEX = 512;
+int INITVAL_INITIALIZER = 0x600D;
+
+A global(GLOBAL_INDEX, INITVAL_INITIALIZER);
+
+int initval = 0;
+__attribute__((constructor)) void set_initval() {
+  initval = INITVAL_INITIALIZER;
+}
+__attribute__((destructor)) void reset_initval() { initval = 0; }
+
+int preinitval = 0;
+void set_preinitval() { preinitval = INITVAL_INITIALIZER; }
+__attribute__((destructor)) void reset_preinitval() { preinitval = 0; }
+
+using PreInitFunc = void();
+__attribute__((section(".preinit_array"))) PreInitFunc *preinit_func_ptr =
+    &set_preinitval;
+
+TEST_MAIN() {
+  ASSERT_EQ(global.get(GLOBAL_INDEX), INITVAL_INITIALIZER);
+  ASSERT_EQ(initval, INITVAL_INITIALIZER);
+  ASSERT_EQ(preinitval, INITVAL_INITIALIZER);
+  return 0;
+}