Merge "bionic: CFIShadow: Remove use of PAGE_SIZE macro." into main
diff --git a/docs/status.md b/docs/status.md
index 5514935..de2fa10 100644
--- a/docs/status.md
+++ b/docs/status.md
@@ -65,6 +65,10 @@
   * New system call wrappers: `__riscv_flush_icache` (`<sys/cachectl.h>`),
     `__riscv_hwprobe` (`<sys/hwprobe.h>`).
 
+New libc behavior in V (API level 35):
+  * Added `LD_SHOW_AUXV` to the dynamic linker to dump the ELF auxiliary
+    vector if the environment variable is set.
+
 New libc functions in U (API level 34):
   * `close_range` and `copy_file_range` (Linux-specific GNU extensions).
   * `memset_explicit` in <string.h> (C23 addition).
diff --git a/libc/async_safe/async_safe_log.cpp b/libc/async_safe/async_safe_log.cpp
index 420560f..2bff616 100644
--- a/libc/async_safe/async_safe_log.cpp
+++ b/libc/async_safe/async_safe_log.cpp
@@ -207,10 +207,12 @@
   // Decode the conversion specifier.
   int is_signed = (conversion == 'd' || conversion == 'i' || conversion == 'o');
   int base = 10;
-  if (conversion == 'x' || conversion == 'X') {
+  if (tolower(conversion) == 'x') {
     base = 16;
   } else if (conversion == 'o') {
     base = 8;
+  } else if (tolower(conversion) == 'b') {
+    base = 2;
   }
   bool caps = (conversion == 'X');
 
@@ -360,7 +362,8 @@
       format_integer(buffer + 2, sizeof(buffer) - 2, value, 'x');
     } else if (c == 'm') {
       strerror_r(errno, buffer, sizeof(buffer));
-    } else if (c == 'd' || c == 'i' || c == 'o' || c == 'u' || c == 'x' || c == 'X') {
+    } else if (tolower(c) == 'b' || c == 'd' || c == 'i' || c == 'o' || c == 'u' ||
+               tolower(c) == 'x') {
       /* integers - first read value from stack */
       uint64_t value;
       int is_signed = (c == 'd' || c == 'i' || c == 'o');
@@ -391,10 +394,10 @@
         value = static_cast<uint64_t>((static_cast<int64_t>(value << shift)) >> shift);
       }
 
-      if (alternate && value != 0 && (c == 'x' || c == 'o')) {
-        if (c == 'x') {
+      if (alternate && value != 0 && (tolower(c) == 'x' || c == 'o' || tolower(c) == 'b')) {
+        if (tolower(c) == 'x' || tolower(c) == 'b') {
           buffer[0] = '0';
-          buffer[1] = 'x';
+          buffer[1] = c;
           format_integer(buffer + 2, sizeof(buffer) - 2, value, c);
         } else {
           buffer[0] = '0';
diff --git a/libc/bionic/bionic_call_ifunc_resolver.cpp b/libc/bionic/bionic_call_ifunc_resolver.cpp
index 410eb78..3cfb8b5 100644
--- a/libc/bionic/bionic_call_ifunc_resolver.cpp
+++ b/libc/bionic/bionic_call_ifunc_resolver.cpp
@@ -58,12 +58,12 @@
   }
   return reinterpret_cast<ifunc_resolver_t>(resolver_addr)(hwcap);
 #elif defined(__riscv)
-  // This argument and its value is just a placeholder for now,
-  // but it means that if we do pass something in future (such as
-  // getauxval() and/or hwprobe key/value pairs), callees will be able to
-  // recognize what they're being given.
-  typedef ElfW(Addr) (*ifunc_resolver_t)(void*);
-  return reinterpret_cast<ifunc_resolver_t>(resolver_addr)(nullptr);
+  // The pointer argument is currently unused, but reserved for future
+  // expansion. If we pass nullptr from the beginning, it'll be easier
+  // to recognize if/when we pass actual data (and matches glibc).
+  typedef ElfW(Addr) (*ifunc_resolver_t)(uint64_t, void*);
+  static uint64_t hwcap = getauxval(AT_HWCAP);
+  return reinterpret_cast<ifunc_resolver_t>(resolver_addr)(hwcap, nullptr);
 #else
   typedef ElfW(Addr) (*ifunc_resolver_t)(void);
   return reinterpret_cast<ifunc_resolver_t>(resolver_addr)();
diff --git a/libc/bionic/libc_init_static.cpp b/libc/bionic/libc_init_static.cpp
index d7a2856..a3c66d4 100644
--- a/libc/bionic/libc_init_static.cpp
+++ b/libc/bionic/libc_init_static.cpp
@@ -356,7 +356,7 @@
       if (memtag_stack) {
         void* pg_start =
             reinterpret_cast<void*>(page_start(reinterpret_cast<uintptr_t>(stack_top)));
-        if (mprotect(pg_start, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_MTE | PROT_GROWSDOWN)) {
+        if (mprotect(pg_start, page_size(), PROT_READ | PROT_WRITE | PROT_MTE | PROT_GROWSDOWN)) {
           async_safe_fatal("error: failed to set PROT_MTE on main thread stack: %m");
         }
       }
diff --git a/libc/bionic/pthread_internal.h b/libc/bionic/pthread_internal.h
index 7efbf6d..3b9e6a4 100644
--- a/libc/bionic/pthread_internal.h
+++ b/libc/bionic/pthread_internal.h
@@ -38,6 +38,8 @@
 #define __hwasan_thread_exit()
 #endif
 
+#include "platform/bionic/page.h"
+
 #include "private/bionic_elf_tls.h"
 #include "private/bionic_lock.h"
 #include "private/bionic_tls.h"
@@ -236,7 +238,7 @@
 // On LP64, we could use more but there's no obvious advantage to doing
 // so, and the various media processes use RLIMIT_AS as a way to limit
 // the amount of allocation they'll do.
-#define PTHREAD_GUARD_SIZE PAGE_SIZE
+#define PTHREAD_GUARD_SIZE max_page_size()
 
 // SIGSTKSZ (8KiB) is not big enough.
 // An snprintf to a stack buffer of size PATH_MAX consumes ~7KiB of stack.
diff --git a/libc/bionic/sysconf.cpp b/libc/bionic/sysconf.cpp
index 3906e2e..edbdef1 100644
--- a/libc/bionic/sysconf.cpp
+++ b/libc/bionic/sysconf.cpp
@@ -41,6 +41,107 @@
 #include "platform/bionic/page.h"
 #include "private/bionic_tls.h"
 
+struct sysconf_cache {
+  long size, assoc, linesize;
+
+  static sysconf_cache from_size_and_geometry(int size_id, int geometry_id) {
+    sysconf_cache result;
+    result.size = getauxval(size_id);
+    unsigned long geometry = getauxval(geometry_id);
+    result.assoc = geometry >> 16;
+    result.linesize = geometry & 0xffff;
+    return result;
+  }
+};
+
+struct sysconf_caches {
+  sysconf_cache l1_i, l1_d, l2, l3, l4;
+};
+
+#if defined(__riscv)
+
+static sysconf_caches* __sysconf_caches() {
+  static sysconf_caches cached = []{
+    sysconf_caches info = {};
+    // riscv64 kernels conveniently hand us all this information.
+    info.l1_i = sysconf_cache::from_size_and_geometry(AT_L1I_CACHESIZE, AT_L1I_CACHEGEOMETRY);
+    info.l1_d = sysconf_cache::from_size_and_geometry(AT_L1D_CACHESIZE, AT_L1D_CACHEGEOMETRY);
+    info.l2 = sysconf_cache::from_size_and_geometry(AT_L2_CACHESIZE, AT_L2_CACHEGEOMETRY);
+    info.l3 = sysconf_cache::from_size_and_geometry(AT_L3_CACHESIZE, AT_L3_CACHEGEOMETRY);
+    return info;
+  }();
+  return &cached;
+}
+
+#elif defined(__aarch64__)
+
+static sysconf_caches* __sysconf_caches() {
+  static sysconf_caches cached = []{
+    sysconf_caches info = {};
+    // arm64 is especially limited. We can infer the L1 line sizes, but that's it.
+    uint64_t ctr_el0;
+    __asm__ __volatile__("mrs %0, ctr_el0" : "=r"(ctr_el0));
+    info.l1_i.linesize = 4 << (ctr_el0 & 0xf);
+    info.l1_d.linesize = 4 << ((ctr_el0 >> 16) & 0xf);
+    return info;
+  }();
+  return &cached;
+}
+
+#else
+
+long __sysconf_fread_long(const char* path) {
+  long result = 0;
+  FILE* fp = fopen(path, "re");
+  if (fp != nullptr) {
+    fscanf(fp, "%ld", &result);
+    fclose(fp);
+  }
+  return result;
+}
+
+static sysconf_caches* __sysconf_caches() {
+  static sysconf_caches cached = []{
+    sysconf_caches info = {};
+    char path[64];
+    for (int i = 0; i < 4; i++) {
+      sysconf_cache c;
+
+      snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu0/cache/index%d/size", i);
+      c.size = __sysconf_fread_long(path) * 1024;
+      if (c.size == 0) break;
+
+      snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu0/cache/index%d/ways_of_associativity", i);
+      c.assoc = __sysconf_fread_long(path);
+
+      snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu0/cache/index%d/coherency_line_size", i);
+      c.linesize = __sysconf_fread_long(path);
+
+      snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu0/cache/index%d/level", i);
+      int level = __sysconf_fread_long(path);
+      if (level == 1) {
+        snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu0/cache/index%d/type", i);
+        FILE* fp = fopen(path, "re");
+        char type = fgetc(fp);
+        fclose(fp);
+        if (type == 'D') {
+          info.l1_d = c;
+        } else if (type == 'I') {
+          info.l1_i = c;
+        }
+      } else if (level == 2) {
+        info.l2 = c;
+      } else if (level == 3) {
+        info.l3 = c;
+      }
+    }
+    return info;
+  }();
+  return &cached;
+}
+
+#endif
+
 static long __sysconf_rlimit(int resource) {
   rlimit rl;
   getrlimit(resource, &rl);
@@ -218,23 +319,21 @@
     case _SC_XOPEN_STREAMS:     return -1;            // Obsolescent in POSIX.1-2008.
     case _SC_XOPEN_UUCP:        return -1;
 
-    // We do not have actual implementations for cache queries.
-    // It's valid to return 0 as the result is unknown.
-    case _SC_LEVEL1_ICACHE_SIZE:      return 0;
-    case _SC_LEVEL1_ICACHE_ASSOC:     return 0;
-    case _SC_LEVEL1_ICACHE_LINESIZE:  return 0;
-    case _SC_LEVEL1_DCACHE_SIZE:      return 0;
-    case _SC_LEVEL1_DCACHE_ASSOC:     return 0;
-    case _SC_LEVEL1_DCACHE_LINESIZE:  return 0;
-    case _SC_LEVEL2_CACHE_SIZE:       return 0;
-    case _SC_LEVEL2_CACHE_ASSOC:      return 0;
-    case _SC_LEVEL2_CACHE_LINESIZE:   return 0;
-    case _SC_LEVEL3_CACHE_SIZE:       return 0;
-    case _SC_LEVEL3_CACHE_ASSOC:      return 0;
-    case _SC_LEVEL3_CACHE_LINESIZE:   return 0;
-    case _SC_LEVEL4_CACHE_SIZE:       return 0;
-    case _SC_LEVEL4_CACHE_ASSOC:      return 0;
-    case _SC_LEVEL4_CACHE_LINESIZE:   return 0;
+    case _SC_LEVEL1_ICACHE_SIZE:      return __sysconf_caches()->l1_i.size;
+    case _SC_LEVEL1_ICACHE_ASSOC:     return __sysconf_caches()->l1_i.assoc;
+    case _SC_LEVEL1_ICACHE_LINESIZE:  return __sysconf_caches()->l1_i.linesize;
+    case _SC_LEVEL1_DCACHE_SIZE:      return __sysconf_caches()->l1_d.size;
+    case _SC_LEVEL1_DCACHE_ASSOC:     return __sysconf_caches()->l1_d.assoc;
+    case _SC_LEVEL1_DCACHE_LINESIZE:  return __sysconf_caches()->l1_d.linesize;
+    case _SC_LEVEL2_CACHE_SIZE:       return __sysconf_caches()->l2.size;
+    case _SC_LEVEL2_CACHE_ASSOC:      return __sysconf_caches()->l2.assoc;
+    case _SC_LEVEL2_CACHE_LINESIZE:   return __sysconf_caches()->l2.linesize;
+    case _SC_LEVEL3_CACHE_SIZE:       return __sysconf_caches()->l3.size;
+    case _SC_LEVEL3_CACHE_ASSOC:      return __sysconf_caches()->l3.assoc;
+    case _SC_LEVEL3_CACHE_LINESIZE:   return __sysconf_caches()->l3.linesize;
+    case _SC_LEVEL4_CACHE_SIZE:       return __sysconf_caches()->l4.size;
+    case _SC_LEVEL4_CACHE_ASSOC:      return __sysconf_caches()->l4.assoc;
+    case _SC_LEVEL4_CACHE_LINESIZE:   return __sysconf_caches()->l4.linesize;
 
     default:
       errno = EINVAL;
diff --git a/libc/include/bits/page_size.h b/libc/include/bits/page_size.h
new file mode 100644
index 0000000..ca434e5
--- /dev/null
+++ b/libc/include/bits/page_size.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+#if !defined(__BIONIC_NO_PAGE_SIZE_MACRO)
+#define PAGE_SIZE 4096
+#define PAGE_MASK (~(PAGE_SIZE - 1))
+#endif
+
+__END_DECLS
diff --git a/libc/include/pthread.h b/libc/include/pthread.h
index 1f08628..4feade5 100644
--- a/libc/include/pthread.h
+++ b/libc/include/pthread.h
@@ -34,6 +34,7 @@
  */
 
 #include <limits.h>
+#include <bits/page_size.h>
 #include <bits/pthread_types.h>
 #include <sched.h>
 #include <sys/cdefs.h>
@@ -73,9 +74,14 @@
 #define PTHREAD_BARRIER_SERIAL_THREAD (-1)
 #endif
 
+
 #if defined(__LP64__)
+#if defined(PAGE_SIZE)
 #define PTHREAD_STACK_MIN (4 * PAGE_SIZE)
 #else
+#define PTHREAD_STACK_MIN 65536
+#endif
+#else
 #define PTHREAD_STACK_MIN (2 * PAGE_SIZE)
 #endif
 
diff --git a/libc/include/setjmp.h b/libc/include/setjmp.h
index 6d047ae..0aaaac5 100644
--- a/libc/include/setjmp.h
+++ b/libc/include/setjmp.h
@@ -66,12 +66,14 @@
 /**
  * The size in words of a riscv64 jmp_buf. Room for callee-saved registers,
  * including floating point, stack pointer and program counter, various
- * internal implementation details, and leaving some free space.
+ * internal implementation details, and leaving lots of free space.
  *
- * Coincidentally matches OpenBSD, though they also save/restore the
- * floating point status register too.
+ * Deliberately very large given the uncertainty around the final form of
+ * hardware shadow stack, and the fact that x86-64 glibc needed to steal
+ * space from their enormous sigset_t (which we don't have) to be able to
+ * implement the CET shadow stack.
  */
-#define _JBLEN 32
+#define _JBLEN 64
 #elif defined(__x86_64__)
 /** The size in words of an x86-64 jmp_buf. Inherited from OpenBSD. */
 #define _JBLEN 11
diff --git a/libc/include/sys/user.h b/libc/include/sys/user.h
index 0ea0285..1d20034 100644
--- a/libc/include/sys/user.h
+++ b/libc/include/sys/user.h
@@ -32,12 +32,9 @@
 #include <stddef.h> /* For size_t. */
 #include <stdint.h>
 
-__BEGIN_DECLS
+#include <bits/page_size.h>
 
-#if !defined(__BIONIC_NO_PAGE_SIZE_MACRO)
-#define PAGE_SIZE 4096
-#define PAGE_MASK (~(PAGE_SIZE - 1))
-#endif
+__BEGIN_DECLS
 
 #if defined(__i386__)
 
diff --git a/libdl/libdl_cfi.cpp b/libdl/libdl_cfi.cpp
index 3b68fc7..23cd7f5 100644
--- a/libdl/libdl_cfi.cpp
+++ b/libdl/libdl_cfi.cpp
@@ -26,15 +26,15 @@
 // dlopen/dlclose.
 static struct {
   uintptr_t v;
-  char padding[PAGE_SIZE - sizeof(v)];
-} shadow_base_storage alignas(PAGE_SIZE);
+  char padding[max_page_size() - sizeof(v)];
+} shadow_base_storage alignas(max_page_size());
 
 // __cfi_init is called by the loader as soon as the shadow is mapped. This may happen very early
 // during startup, before libdl.so global constructors, and, on i386, even before __libc_sysinfo is
 // initialized. This function should not do any system calls.
 extern "C" uintptr_t* __cfi_init(uintptr_t shadow_base) {
   shadow_base_storage.v = shadow_base;
-  static_assert(sizeof(shadow_base_storage) == PAGE_SIZE, "");
+  static_assert(sizeof(shadow_base_storage) == max_page_size(), "");
   return &shadow_base_storage.v;
 }
 
diff --git a/linker/Android.bp b/linker/Android.bp
index 020bd7d..0ccd16d 100644
--- a/linker/Android.bp
+++ b/linker/Android.bp
@@ -169,6 +169,7 @@
     srcs: [
         "dlfcn.cpp",
         "linker.cpp",
+        "linker_auxv.cpp",
         "linker_block_allocator.cpp",
         "linker_dlwarning.cpp",
         "linker_cfi.cpp",
diff --git a/linker/NOTICE b/linker/NOTICE
index d61a193..7fd1877 100644
--- a/linker/NOTICE
+++ b/linker/NOTICE
@@ -362,3 +362,31 @@
 
 -------------------------------------------------------------------
 
+Copyright (C) 2023 The Android Open Source Project
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+ * Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in
+   the documentation and/or other materials provided with the
+   distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+-------------------------------------------------------------------
+
diff --git a/linker/linker_auxv.cpp b/linker/linker_auxv.cpp
new file mode 100644
index 0000000..d8e4a3e
--- /dev/null
+++ b/linker/linker_auxv.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "linker_auxv.h"
+
+#include <elf.h>
+#include <stdio.h>
+#include <sys/auxv.h>
+#include <unistd.h>
+
+#include <async_safe/log.h>
+
+static const char* auxv_name(int at) {
+  switch (at) {
+  case AT_NULL: return "AT_NULL";
+  case AT_IGNORE: return "AT_IGNORE";
+  case AT_EXECFD: return "AT_EXECFD";
+  case AT_PHDR: return "AT_PHDR";
+  case AT_PHENT: return "AT_PHENT";
+  case AT_PHNUM: return "AT_PHNUM";
+  case AT_PAGESZ: return "AT_PAGESZ";
+  case AT_BASE: return "AT_BASE";
+  case AT_FLAGS: return "AT_FLAGS";
+  case AT_ENTRY: return "AT_ENTRY";
+  case AT_NOTELF: return "AT_NOTELF";
+  case AT_UID: return "AT_UID";
+  case AT_EUID: return "AT_EUID";
+  case AT_GID: return "AT_GID";
+  case AT_EGID: return "AT_EGID";
+  case AT_PLATFORM: return "AT_PLATFORM";
+  case AT_HWCAP: return "AT_HWCAP";
+  case AT_CLKTCK: return "AT_CLKTCK";
+  case AT_SECURE: return "AT_SECURE";
+  case AT_BASE_PLATFORM: return "AT_BASE_PLATFORM";
+  case AT_RANDOM: return "AT_RANDOM";
+  case AT_HWCAP2: return "AT_HWCAP2";
+  case AT_RSEQ_FEATURE_SIZE: return "AT_RSEQ_FEATURE_SIZE";
+  case AT_RSEQ_ALIGN: return "AT_RSEQ_ALIGN";
+  case AT_EXECFN: return "AT_EXECFN";
+  case AT_SYSINFO_EHDR: return "AT_SYSINFO_EHDR";
+#if defined(AT_MINSIGSTKSZ)
+  case AT_MINSIGSTKSZ: return "AT_MINSIGSTKSZ";
+#endif
+#if defined(AT_SYSINFO)
+  case AT_SYSINFO: return "AT_SYSINFO";
+#endif
+#if defined(AT_L1I_CACHESIZE)
+  case AT_L1I_CACHESIZE: return "AT_L1I_CACHESIZE";
+#endif
+#if defined(AT_L1I_CACHEGEOMETRY)
+  case AT_L1I_CACHEGEOMETRY: return "AT_L1I_CACHEGEOMETRY";
+#endif
+#if defined(AT_L1D_CACHESIZE)
+  case AT_L1D_CACHESIZE: return "AT_L1D_CACHESIZE";
+#endif
+#if defined(AT_L1D_CACHEGEOMETRY)
+  case AT_L1D_CACHEGEOMETRY: return "AT_L1D_CACHEGEOMETRY";
+#endif
+#if defined(AT_L2_CACHESIZE)
+  case AT_L2_CACHESIZE: return "AT_L2_CACHESIZE";
+#endif
+#if defined(AT_L2_CACHEGEOMETRY)
+  case AT_L2_CACHEGEOMETRY: return "AT_L2_CACHEGEOMETRY";
+#endif
+  }
+  static char name[32];
+  snprintf(name, sizeof(name), "AT_??? (%d)", at);
+  return name;
+}
+
+void ld_show_auxv(ElfW(auxv_t)* auxv) {
+  for (ElfW(auxv_t)* v = auxv; v->a_type != AT_NULL; ++v) {
+    const char* name = auxv_name(v->a_type);
+    long value = v->a_un.a_val;
+    switch (v->a_type) {
+    case AT_SYSINFO_EHDR:
+    case AT_PHDR:
+    case AT_BASE:
+    case AT_ENTRY:
+    case AT_RANDOM:
+      async_safe_format_fd(STDOUT_FILENO, "%-20s %#lx\n", name, value);
+      break;
+    case AT_FLAGS:
+    case AT_HWCAP:
+    case AT_HWCAP2:
+      async_safe_format_fd(STDOUT_FILENO, "%-20s %#lb\n", name, value);
+      break;
+    case AT_EXECFN:
+    case AT_PLATFORM:
+      async_safe_format_fd(STDOUT_FILENO, "%-20s \"%s\"\n", name, reinterpret_cast<char*>(value));
+      break;
+    default:
+      async_safe_format_fd(STDOUT_FILENO, "%-20s %ld\n", name, value);
+      break;
+    }
+  }
+}
diff --git a/linker/linker_auxv.h b/linker/linker_auxv.h
new file mode 100644
index 0000000..c093283
--- /dev/null
+++ b/linker/linker_auxv.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <elf.h>
+#include <link.h>
+
+void ld_show_auxv(ElfW(auxv_t)* auxv);
diff --git a/linker/linker_main.cpp b/linker/linker_main.cpp
index e92aada..5a33a63 100644
--- a/linker/linker_main.cpp
+++ b/linker/linker_main.cpp
@@ -32,6 +32,7 @@
 #include <sys/auxv.h>
 
 #include "linker.h"
+#include "linker_auxv.h"
 #include "linker_cfi.h"
 #include "linker_debug.h"
 #include "linker_debuggerd.h"
@@ -44,7 +45,6 @@
 #include "linker_utils.h"
 
 #include "private/KernelArgumentBlock.h"
-#include "private/ScopedPthreadMutexLocker.h"
 #include "private/bionic_call_ifunc_resolver.h"
 #include "private/bionic_globals.h"
 #include "private/bionic_tls.h"
@@ -325,12 +325,14 @@
 
   g_linker_logger.ResetState();
 
-  // Get a few environment variables.
+  // Enable debugging logs?
   const char* LD_DEBUG = getenv("LD_DEBUG");
   if (LD_DEBUG != nullptr) {
     g_ld_debug_verbosity = atoi(LD_DEBUG);
   }
 
+  if (getenv("LD_SHOW_AUXV") != nullptr) ld_show_auxv(args.auxv);
+
 #if defined(__LP64__)
   INFO("[ Android dynamic linker (64-bit) ]");
 #else
@@ -499,11 +501,6 @@
 
   if (!get_cfi_shadow()->InitialLinkDone(solist)) __linker_cannot_link(g_argv[0]);
 
-  // A constructor could spawn a thread that calls into the loader, so as soon
-  // as we've called a constructor, we need to hold the lock while accessing
-  // global loader state.
-  ScopedPthreadMutexLocker locker(&g_dl_mutex);
-
   si->call_pre_init_constructors();
   si->call_constructors();
 
@@ -696,6 +693,13 @@
  * function, or other GOT reference will generate a segfault.
  */
 extern "C" ElfW(Addr) __linker_init(void* raw_args) {
+  // Unlock the loader mutex immediately before transferring to the executable's
+  // entry point. This must happen after destructors are called in this function
+  // (e.g. ~soinfo), so declare this variable very early.
+  struct DlMutexUnlocker {
+    ~DlMutexUnlocker() { pthread_mutex_unlock(&g_dl_mutex); }
+  } unlocker;
+
   // Initialize TLS early so system calls and errno work.
   KernelArgumentBlock args(raw_args);
   bionic_tcb temp_tcb __attribute__((uninitialized));
@@ -758,6 +762,11 @@
   // Initialize the linker's static libc's globals
   __libc_init_globals();
 
+  // A constructor could spawn a thread that calls into the loader, so as soon
+  // as we've called a constructor, we need to hold the lock until transferring
+  // to the entry point.
+  pthread_mutex_lock(&g_dl_mutex);
+
   // Initialize the linker's own global variables
   tmp_linker_so.call_constructors();
 
diff --git a/tests/async_safe_test.cpp b/tests/async_safe_test.cpp
index dc4db07..cc1b598 100644
--- a/tests/async_safe_test.cpp
+++ b/tests/async_safe_test.cpp
@@ -45,6 +45,15 @@
   async_safe_format_buffer(buf, sizeof(buf), "aa%scc", "bb");
   EXPECT_STREQ("aabbcc", buf);
 
+  async_safe_format_buffer(buf, sizeof(buf), "a%bb", 1234);
+  EXPECT_STREQ("a10011010010b", buf);
+
+  async_safe_format_buffer(buf, sizeof(buf), "a%#bb", 1234);
+  EXPECT_STREQ("a0b10011010010b", buf);
+
+  async_safe_format_buffer(buf, sizeof(buf), "a%#Bb", 1234);
+  EXPECT_STREQ("a0B10011010010b", buf);
+
   async_safe_format_buffer(buf, sizeof(buf), "a%cc", 'b');
   EXPECT_STREQ("abc", buf);
 
@@ -76,9 +85,15 @@
   async_safe_format_buffer(buf, sizeof(buf), "a%xz", 0x12ab);
   EXPECT_STREQ("a12abz", buf);
 
+  async_safe_format_buffer(buf, sizeof(buf), "a%#xz", 0x12ab);
+  EXPECT_STREQ("a0x12abz", buf);
+
   async_safe_format_buffer(buf, sizeof(buf), "a%Xz", 0x12ab);
   EXPECT_STREQ("a12ABz", buf);
 
+  async_safe_format_buffer(buf, sizeof(buf), "a%#Xz", 0x12ab);
+  EXPECT_STREQ("a0X12ABz", buf);
+
   async_safe_format_buffer(buf, sizeof(buf), "a%08xz", 0x123456);
   EXPECT_STREQ("a00123456z", buf);
 
diff --git a/tests/ifunc_test.cpp b/tests/ifunc_test.cpp
index e3c437e..1fdbf1a 100644
--- a/tests/ifunc_test.cpp
+++ b/tests/ifunc_test.cpp
@@ -60,6 +60,26 @@
   return ret42;
 }
 
+#elif defined(__riscv)
+
+#include <sys/hwprobe.h>
+
+static uint64_t g_hwcap;
+
+static riscv_hwprobe g_hwprobes[] = {{.key = RISCV_HWPROBE_KEY_IMA_EXT_0}};
+
+extern "C" fn_ptr_t hwcap_resolver(uint64_t hwcap, void* null) {
+  // Check hwcap like arm32/arm64.
+  g_hwcap = hwcap;
+
+  // For now, the pointer argument is reserved for future expansion.
+  if (null != NULL) abort();
+
+  // Ensure that __riscv_hwprobe() can be called from an ifunc.
+  if (__riscv_hwprobe(g_hwprobes, 1, 0, nullptr, 0) != 0) return nullptr;
+  return ret42;
+}
+
 #else
 
 extern "C" fn_ptr_t hwcap_resolver() {
@@ -81,6 +101,12 @@
   EXPECT_EQ(getauxval(AT_HWCAP2), g_arg._hwcap2);
 #elif defined(__arm__)
   EXPECT_EQ(getauxval(AT_HWCAP), g_hwcap);
+#elif defined(__riscv)
+  EXPECT_EQ(getauxval(AT_HWCAP), g_hwcap);
+
+  riscv_hwprobe probes[] = {{.key = RISCV_HWPROBE_KEY_IMA_EXT_0}};
+  ASSERT_EQ(0, __riscv_hwprobe(probes, 1, 0, nullptr, 0));
+  EXPECT_EQ(probes[0].value, g_hwprobes[0].value);
 #endif
 }
 
diff --git a/tests/sys_hwprobe_test.cpp b/tests/sys_hwprobe_test.cpp
index 15028ff..a4b47c7 100644
--- a/tests/sys_hwprobe_test.cpp
+++ b/tests/sys_hwprobe_test.cpp
@@ -30,10 +30,11 @@
 
 #if __has_include(<sys/hwprobe.h>)
 #include <sys/hwprobe.h>
+#include <sys/syscall.h>
 #endif
 
 TEST(sys_hwprobe, __riscv_hwprobe) {
-#if defined(__riscv) && __has_include(<sys/cachectl.h>)
+#if defined(__riscv) && __has_include(<sys/hwprobe.h>)
   riscv_hwprobe probes[] = {{.key = RISCV_HWPROBE_KEY_IMA_EXT_0},
                             {.key = RISCV_HWPROBE_KEY_CPUPERF_0}};
   ASSERT_EQ(0, __riscv_hwprobe(probes, 2, 0, nullptr, 0));
@@ -60,3 +61,25 @@
   GTEST_SKIP() << "__riscv_hwprobe requires riscv64";
 #endif
 }
+
+TEST(sys_hwprobe, __riscv_hwprobe_syscall_vdso) {
+#if defined(__riscv) && __has_include(<sys/hwprobe.h>)
+  riscv_hwprobe probes_vdso[] = {{.key = RISCV_HWPROBE_KEY_IMA_EXT_0},
+                                 {.key = RISCV_HWPROBE_KEY_CPUPERF_0}};
+  ASSERT_EQ(0, __riscv_hwprobe(probes_vdso, 2, 0, nullptr, 0));
+
+  riscv_hwprobe probes_syscall[] = {{.key = RISCV_HWPROBE_KEY_IMA_EXT_0},
+                                    {.key = RISCV_HWPROBE_KEY_CPUPERF_0}};
+  ASSERT_EQ(0, syscall(SYS_riscv_hwprobe, probes_syscall, 2, 0, nullptr, 0));
+
+  // Check we got the same answers from the vdso and the syscall.
+  EXPECT_EQ(RISCV_HWPROBE_KEY_IMA_EXT_0, probes_syscall[0].key);
+  EXPECT_EQ(probes_vdso[0].key, probes_syscall[0].key);
+  EXPECT_EQ(probes_vdso[0].value, probes_syscall[0].value);
+  EXPECT_EQ(RISCV_HWPROBE_KEY_CPUPERF_0, probes_syscall[1].key);
+  EXPECT_EQ(probes_vdso[1].key, probes_syscall[1].key);
+  EXPECT_EQ(probes_vdso[1].value, probes_syscall[1].value);
+#else
+  GTEST_SKIP() << "__riscv_hwprobe requires riscv64";
+#endif
+}
diff --git a/tests/unistd_test.cpp b/tests/unistd_test.cpp
index 4c21627..b639a4e 100644
--- a/tests/unistd_test.cpp
+++ b/tests/unistd_test.cpp
@@ -1166,6 +1166,26 @@
   VERIFY_SYSCONF_UNKNOWN(666);
 }
 
+static void show_cache(const char* name, long size, long assoc, long line_size) {
+  printf("%s cache size: %ld bytes, line size %ld bytes, ", name, size, line_size);
+  if (assoc == 0) {
+    printf("fully");
+  } else {
+    printf("%ld-way", assoc);
+  }
+  printf(" associative\n");
+}
+
+TEST(UNISTD_TEST, sysconf_cache) {
+  // It's not obvious we can _test_ any of these, but we can at least
+  // show the output for humans to inspect.
+  show_cache("L1D", sysconf(_SC_LEVEL1_DCACHE_SIZE), sysconf(_SC_LEVEL1_DCACHE_ASSOC), sysconf(_SC_LEVEL1_DCACHE_LINESIZE));
+  show_cache("L1I", sysconf(_SC_LEVEL1_ICACHE_SIZE), sysconf(_SC_LEVEL1_ICACHE_ASSOC), sysconf(_SC_LEVEL1_ICACHE_LINESIZE));
+  show_cache("L2", sysconf(_SC_LEVEL2_CACHE_SIZE), sysconf(_SC_LEVEL2_CACHE_ASSOC), sysconf(_SC_LEVEL2_CACHE_LINESIZE));
+  show_cache("L3", sysconf(_SC_LEVEL3_CACHE_SIZE), sysconf(_SC_LEVEL3_CACHE_ASSOC), sysconf(_SC_LEVEL3_CACHE_LINESIZE));
+  show_cache("L4", sysconf(_SC_LEVEL4_CACHE_SIZE), sysconf(_SC_LEVEL4_CACHE_ASSOC), sysconf(_SC_LEVEL4_CACHE_LINESIZE));
+}
+
 TEST(UNISTD_TEST, dup2_same) {
   // POSIX says of dup2:
   // If fildes2 is already a valid open file descriptor ...