Skip to content

[libc] implement sys/getauxval #78493

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jan 23, 2024

Conversation

SchrodingerZhu
Copy link
Contributor

@SchrodingerZhu SchrodingerZhu commented Jan 17, 2024

As there are usages in overlay mode. This PR implements sys/getauxval that can be used in both overlay builds and full builds.

@SchrodingerZhu
Copy link
Contributor Author

@nickdesaulniers there is a complication that internal atexit may not exist. Should I try calling extern "C" version instead?

@nickdesaulniers
Copy link
Member

cc @@llvm/pr-subscribers-libc (perhaps there's a better list)

I suspect that for baremetal targets, we should document a shortlist of functions that their runtime MUST provide. @Prabhuk or @petrhosek might have some thoughts about this, too.

@michaelrj-google is in the middle of some design discussions around abstracting I/O that might also be relevant here.

@michaelrj-google
Copy link
Contributor

The I/O discussions aren't super relevant to this change, though I appreciate asking. The I/O discussions mostly centered on enabling a mode with no function pointers (discussed here: https://discourse.llvm.org/t/rfc-modernizing-and-formalizing-the-file-abstraction-interface/70605/6) and the other pieces of the FILE abstraction that need to be finished (i.e. wide chars, buffer flushing when reading)

@petrhosek
Copy link
Member

Once this lands, I'd like to use it in the implementation of #78804.

@SchrodingerZhu SchrodingerZhu marked this pull request as ready for review January 22, 2024 19:27
@llvmbot llvmbot added the libc label Jan 22, 2024
@llvmbot
Copy link
Member

llvmbot commented Jan 22, 2024

@llvm/pr-subscribers-libc

Author: Schrodinger ZHU Yifan (SchrodingerZhu)

Changes

WIP, tests are to be added.


Full diff: https://github.com/llvm/llvm-project/pull/78493.diff

14 Files Affected:

  • (modified) libc/config/linux/aarch64/entrypoints.txt (+3)
  • (modified) libc/config/linux/app.h (+1-1)
  • (modified) libc/config/linux/arm/entrypoints.txt (+3)
  • (modified) libc/config/linux/riscv/entrypoints.txt (+3)
  • (modified) libc/config/linux/x86_64/entrypoints.txt (+3)
  • (modified) libc/src/sys/CMakeLists.txt (+1)
  • (added) libc/src/sys/auxv/CMakeLists.txt (+10)
  • (added) libc/src/sys/auxv/getauxval.h (+20)
  • (added) libc/src/sys/auxv/linux/CMakeLists.txt (+18)
  • (added) libc/src/sys/auxv/linux/getauxval.cpp (+196)
  • (modified) libc/test/src/sys/CMakeLists.txt (+1)
  • (added) libc/test/src/sys/auxv/CMakeLists.txt (+3)
  • (added) libc/test/src/sys/auxv/linux/CMakeLists.txt (+14)
  • (added) libc/test/src/sys/auxv/linux/getauxval_test.cpp (+27)
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 625fa6bffe63c65..3f66a582f5e3ee3 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -168,6 +168,9 @@ set(TARGET_LIBC_ENTRYPOINTS
     # sys/prctl.h entrypoints
     libc.src.sys.prctl.prctl
 
+    # sys/auxv.h entrypoints
+    libc.src.sys.auxv.getauxval
+
     # termios.h entrypoints
     libc.src.termios.cfgetispeed
     libc.src.termios.cfgetospeed
diff --git a/libc/config/linux/app.h b/libc/config/linux/app.h
index 1b3523deb1b23ec..766cd49e88f6f76 100644
--- a/libc/config/linux/app.h
+++ b/libc/config/linux/app.h
@@ -93,7 +93,7 @@ struct AppProperties {
   AuxEntry *auxv_ptr;
 };
 
-extern AppProperties app;
+[[gnu::weak]] extern AppProperties app;
 
 // The descriptor of a thread's TLS area.
 struct TLSDescriptor {
diff --git a/libc/config/linux/arm/entrypoints.txt b/libc/config/linux/arm/entrypoints.txt
index c75ac2302d4ac45..301870d337ca007 100644
--- a/libc/config/linux/arm/entrypoints.txt
+++ b/libc/config/linux/arm/entrypoints.txt
@@ -95,6 +95,9 @@ set(TARGET_LIBC_ENTRYPOINTS
 
     # sys/prctl.h entrypoints
     libc.src.sys.prctl.prctl
+
+    # sys/auxv.h entrypoints
+    libc.src.sys.auxv.getauxval
 )
 
 set(TARGET_LIBM_ENTRYPOINTS
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index ec2a16f5cf473bd..0331ef782cf74a8 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -174,6 +174,9 @@ set(TARGET_LIBC_ENTRYPOINTS
     # sys/prctl.h entrypoints
     libc.src.sys.prctl.prctl
 
+    # sys/auxv.h entrypoints
+    libc.src.sys.auxv.getauxval
+
     # termios.h entrypoints
     libc.src.termios.cfgetispeed
     libc.src.termios.cfgetospeed
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 094bdde2e1589cb..d5ab891674a2d86 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -174,6 +174,9 @@ set(TARGET_LIBC_ENTRYPOINTS
     # sys/prctl.h entrypoints
     libc.src.sys.prctl.prctl
 
+    # sys/auxv.h entrypoints
+    libc.src.sys.auxv.getauxval
+
     # termios.h entrypoints
     libc.src.termios.cfgetispeed
     libc.src.termios.cfgetospeed
diff --git a/libc/src/sys/CMakeLists.txt b/libc/src/sys/CMakeLists.txt
index 12e2020f013ab12..81098294176ad5b 100644
--- a/libc/src/sys/CMakeLists.txt
+++ b/libc/src/sys/CMakeLists.txt
@@ -1,3 +1,4 @@
+add_subdirectory(auxv)
 add_subdirectory(mman)
 add_subdirectory(random)
 add_subdirectory(resource)
diff --git a/libc/src/sys/auxv/CMakeLists.txt b/libc/src/sys/auxv/CMakeLists.txt
new file mode 100644
index 000000000000000..4065761064b129a
--- /dev/null
+++ b/libc/src/sys/auxv/CMakeLists.txt
@@ -0,0 +1,10 @@
+if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
+  add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
+endif()
+
+add_entrypoint_object(
+  getauxval
+  ALIAS
+  DEPENDS
+    .${LIBC_TARGET_OS}.getauxval
+)
diff --git a/libc/src/sys/auxv/getauxval.h b/libc/src/sys/auxv/getauxval.h
new file mode 100644
index 000000000000000..7c9fb846e919841
--- /dev/null
+++ b/libc/src/sys/auxv/getauxval.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for getauxval function ------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_SYS_AUXV_GETAUXVAL_H
+#define LLVM_LIBC_SRC_SYS_AUXV_GETAUXVAL_H
+
+#include <sys/auxv.h>
+
+namespace LIBC_NAMESPACE {
+
+unsigned long getauxval(unsigned long id);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_SYS_AUXV_GETAUXVAL_H
diff --git a/libc/src/sys/auxv/linux/CMakeLists.txt b/libc/src/sys/auxv/linux/CMakeLists.txt
new file mode 100644
index 000000000000000..b38d63ee0329c79
--- /dev/null
+++ b/libc/src/sys/auxv/linux/CMakeLists.txt
@@ -0,0 +1,18 @@
+add_entrypoint_object(
+  getauxval
+  SRCS
+  getauxval.cpp
+  HDRS
+    ../getauxval.h
+  DEPENDS
+    libc.src.sys.prctl.prctl
+    libc.src.sys.mman.mmap
+    libc.src.sys.mman.munmap
+    libc.src.__support.threads.callonce
+    libc.src.__support.common
+    libc.src.errno.errno
+    libc.config.linux.app_h
+    libc.src.fcntl.open
+    libc.src.unistd.read
+    libc.src.unistd.close
+)
diff --git a/libc/src/sys/auxv/linux/getauxval.cpp b/libc/src/sys/auxv/linux/getauxval.cpp
new file mode 100644
index 000000000000000..4b2aabe40c39b5a
--- /dev/null
+++ b/libc/src/sys/auxv/linux/getauxval.cpp
@@ -0,0 +1,196 @@
+//===-- Implementation file for getauxval function --------------*- C++ -*-===//
+//
+// 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 "src/sys/auxv/getauxval.h"
+#include "config/linux/app.h"
+#include "src/__support/common.h"
+#include "src/errno/libc_errno.h"
+#include <linux/auxvec.h>
+
+// for guarded initialization
+#include "src/__support/threads/callonce.h"
+#include "src/__support/threads/linux/futex_word.h"
+
+// for mallocing the global auxv
+#include "src/sys/mman/mmap.h"
+#include "src/sys/mman/munmap.h"
+
+// for reading /proc/self/auxv
+#include "src/fcntl/open.h"
+#include "src/sys/prctl/prctl.h"
+#include "src/unistd/close.h"
+#include "src/unistd/read.h"
+
+// getauxval will work either with or without __cxa_atexit support.
+// In order to detect if __cxa_atexit is supported, we define a weak symbol.
+// We prefer __cxa_atexit as it is always defined as a C symbol whileas atexit
+// may not be created via objcopy yet.
+extern "C" [[gnu::weak]] int __cxa_atexit(void (*callback)(void *),
+                                          void *payload, void *);
+
+namespace LIBC_NAMESPACE {
+
+constexpr static size_t MAX_AUXV_ENTRIES = 64;
+
+// Helper to recover or set errno
+struct AuxvErrnoGuard {
+  int saved;
+  bool failure;
+  AuxvErrnoGuard() : saved(libc_errno), failure(false) {}
+  ~AuxvErrnoGuard() { libc_errno = failure ? ENOENT : saved; }
+  void mark_failure() { failure = true; }
+};
+
+// Helper to manage the memory
+static AuxEntry *auxv = nullptr;
+
+struct AuxvMMapGuard {
+  constexpr static size_t AUXV_MMAP_SIZE = sizeof(AuxEntry) * MAX_AUXV_ENTRIES;
+  void *ptr;
+  AuxvMMapGuard()
+      : ptr(mmap(nullptr, AUXV_MMAP_SIZE, PROT_READ | PROT_WRITE,
+                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)) {}
+  ~AuxvMMapGuard() {
+    if (ptr != MAP_FAILED) {
+      munmap(ptr, AUXV_MMAP_SIZE);
+    }
+  }
+  void submit_to_global() {
+    // atexit may fail, we do not set it to global in that case.
+    int ret = __cxa_atexit(
+        [](void *) {
+          munmap(auxv, AUXV_MMAP_SIZE);
+          auxv = nullptr;
+        },
+        nullptr, nullptr);
+
+    if (ret != 0)
+      return;
+
+    auxv = reinterpret_cast<AuxEntry *>(ptr);
+    ptr = MAP_FAILED;
+  }
+  bool allocated() { return ptr != MAP_FAILED; }
+};
+
+struct AuxvFdGuard {
+  int fd;
+  AuxvFdGuard() : fd(open("/proc/self/auxv", O_RDONLY | O_CLOEXEC)) {}
+  ~AuxvFdGuard() {
+    if (fd != -1) {
+      close(fd);
+    }
+  }
+  bool valid() { return fd != -1; }
+};
+
+static void initialize_auxv_once(void) {
+  // if we cannot get atexit, we cannot register the cleanup function.
+  if (&__cxa_atexit == nullptr)
+    return;
+
+  AuxvMMapGuard mmap_guard;
+  if (!mmap_guard.allocated())
+    return;
+  auto *ptr = reinterpret_cast<AuxEntry *>(mmap_guard.ptr);
+
+  // We get one less than the max size to make sure the search always
+  // terminates. MMAP private pages are zeroed out already.
+  size_t available_size = AuxvMMapGuard::AUXV_MMAP_SIZE - sizeof(AuxEntryType);
+#if defined(PR_GET_AUXV)
+  int ret = prctl(PR_GET_AUXV, reinterpret_cast<unsigned long>(ptr),
+                  available_size, 0, 0);
+  if (ret >= 0) {
+    mmap_guard.submit_to_global();
+    return;
+  }
+#endif
+  AuxvFdGuard fd_guard;
+  if (!fd_guard.valid())
+    return;
+  auto *buf = reinterpret_cast<char *>(ptr);
+  libc_errno = 0;
+  bool error_detected = false;
+  while (available_size != 0) {
+    ssize_t bytes_read = read(fd_guard.fd, buf, available_size);
+    if (bytes_read <= 0) {
+      if (libc_errno == EINTR)
+        continue;
+      error_detected = bytes_read < 0;
+      break;
+    }
+    available_size -= bytes_read;
+  }
+  if (!error_detected) {
+    mmap_guard.submit_to_global();
+  }
+}
+
+static AuxEntry read_entry(int fd) {
+  AuxEntry buf;
+  ssize_t size = sizeof(AuxEntry);
+  while (size > 0) {
+    ssize_t ret = read(fd, &buf, size);
+    if (ret < 0) {
+      if (libc_errno == EINTR)
+        continue;
+      buf.id = AT_NULL;
+      buf.value = AT_NULL;
+      break;
+    }
+    size -= ret;
+  }
+  return buf;
+}
+
+LLVM_LIBC_FUNCTION(unsigned long, getauxval, (unsigned long id)) {
+  // Fast path when libc is loaded by its own initialization code. In this case,
+  // app.auxv_ptr is already set to the auxv passed on the initial stack of the
+  // process.
+  AuxvErrnoGuard errno_guard;
+
+  auto search_auxv = [&errno_guard](AuxEntry *auxv,
+                                    unsigned long id) -> AuxEntryType {
+    for (auto *ptr = auxv; ptr->id != AT_NULL; ptr++) {
+      if (ptr->id == id) {
+        return ptr->value;
+      }
+    }
+    errno_guard.mark_failure();
+    return AT_NULL;
+  };
+
+  // App is a weak symbol that is only defined if libc is linked to its own
+  // initialization routine. We need to check if it is null.
+  if (&app != nullptr) {
+    return search_auxv(app.auxv_ptr, id);
+  }
+
+  static FutexWordType once_flag;
+  callonce(reinterpret_cast<CallOnceFlag *>(&once_flag), initialize_auxv_once);
+  if (auxv != nullptr) {
+    return search_auxv(auxv, id);
+  }
+
+  // fallback to use read without mmap
+  AuxvFdGuard fd_guard;
+  if (fd_guard.valid()) {
+    while (true) {
+      AuxEntry buf = read_entry(fd_guard.fd);
+      if (buf.id == AT_NULL)
+        break;
+      if (buf.id == id)
+        return buf.value;
+    }
+  }
+
+  // cannot find the entry after all methods, mark failure and return 0
+  errno_guard.mark_failure();
+  return AT_NULL;
+}
+} // namespace LIBC_NAMESPACE
diff --git a/libc/test/src/sys/CMakeLists.txt b/libc/test/src/sys/CMakeLists.txt
index a87e77da7d2cdb7..c7095456383b300 100644
--- a/libc/test/src/sys/CMakeLists.txt
+++ b/libc/test/src/sys/CMakeLists.txt
@@ -8,3 +8,4 @@ add_subdirectory(stat)
 add_subdirectory(utsname)
 add_subdirectory(wait)
 add_subdirectory(prctl)
+add_subdirectory(auxv)
diff --git a/libc/test/src/sys/auxv/CMakeLists.txt b/libc/test/src/sys/auxv/CMakeLists.txt
new file mode 100644
index 000000000000000..b4bbe81c92ff2eb
--- /dev/null
+++ b/libc/test/src/sys/auxv/CMakeLists.txt
@@ -0,0 +1,3 @@
+if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
+  add_subdirectory(${LIBC_TARGET_OS})
+endif()
diff --git a/libc/test/src/sys/auxv/linux/CMakeLists.txt b/libc/test/src/sys/auxv/linux/CMakeLists.txt
new file mode 100644
index 000000000000000..c1e82a1f0a46c3c
--- /dev/null
+++ b/libc/test/src/sys/auxv/linux/CMakeLists.txt
@@ -0,0 +1,14 @@
+add_custom_target(libc_sys_auxv_unittests)
+add_libc_unittest(
+  getauxval_test
+  SUITE
+    libc_sys_auxv_unittests
+  SRCS
+    getauxval_test.cpp
+  DEPENDS
+    libc.include.sys_auxv
+    libc.src.errno.errno
+    libc.src.sys.auxv.getauxval
+    libc.test.UnitTest.ErrnoSetterMatcher
+    libc.src.string.strstr
+)
diff --git a/libc/test/src/sys/auxv/linux/getauxval_test.cpp b/libc/test/src/sys/auxv/linux/getauxval_test.cpp
new file mode 100644
index 000000000000000..3b0c4e1b4175f28
--- /dev/null
+++ b/libc/test/src/sys/auxv/linux/getauxval_test.cpp
@@ -0,0 +1,27 @@
+//===-- Unittests for getaxuval -------------------------------------------===//
+//
+// 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 "src/errno/libc_errno.h"
+#include "src/sys/auxv/getauxval.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#include "test/UnitTest/Test.h"
+#include <src/string/strstr.h>
+#include <sys/auxv.h>
+
+using namespace LIBC_NAMESPACE::testing::ErrnoSetterMatcher;
+
+TEST(LlvmLibcGetauxvalTest, Basic) {
+  EXPECT_THAT(LIBC_NAMESPACE::getauxval(AT_PAGESZ), returns(GT(0ul)));
+  const char *filename;
+  auto getfilename = [&filename]() {
+    auto value = LIBC_NAMESPACE::getauxval(AT_EXECFN);
+    filename = reinterpret_cast<const char *>(value);
+    return value;
+  };
+  EXPECT_THAT(getfilename(), returns(NE(0ul)));
+  ASSERT_TRUE(LIBC_NAMESPACE::strstr(filename, "getauxval_test") != nullptr);
+}

@SchrodingerZhu
Copy link
Contributor Author

I failed to reproduce the error locally. let me check what asan say.

@SchrodingerZhu SchrodingerZhu merged commit 4beb723 into llvm:main Jan 23, 2024
@SchrodingerZhu SchrodingerZhu deleted the libc/getauxval-impl branch January 23, 2024 02:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants