-
Notifications
You must be signed in to change notification settings - Fork 13.7k
[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
[libc] implement sys/getauxval #78493
Conversation
@nickdesaulniers there is a complication that internal |
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. |
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) |
Once this lands, I'd like to use it in the implementation of #78804. |
0478641
to
613adc2
Compare
613adc2
to
579c5ae
Compare
d8997d3
to
0e2fcff
Compare
@llvm/pr-subscribers-libc Author: Schrodinger ZHU Yifan (SchrodingerZhu) ChangesWIP, tests are to be added. Full diff: https://github.com/llvm/llvm-project/pull/78493.diff 14 Files Affected:
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);
+}
|
I failed to reproduce the error locally. let me check what asan say. |
As there are usages in overlay mode. This PR implements
sys/getauxval
that can be used in both overlay builds and full builds.