Merge "Remove versioner." into main
diff --git a/cpu_target_features/Android.bp b/cpu_target_features/Android.bp
new file mode 100644
index 0000000..25f37d1
--- /dev/null
+++ b/cpu_target_features/Android.bp
@@ -0,0 +1,18 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "cpu-target-features",
+    srcs: [
+        "main.cpp",
+    ],
+    generated_headers: ["print_target_features.inc"],
+}
+
+genrule {
+    name: "print_target_features.inc",
+    out: ["print_target_features.inc"],
+    tool_files: ["generate_printer.py"],
+    cmd: "$(location generate_printer.py) $(out)",
+}
diff --git a/cpu_target_features/generate_printer.py b/cpu_target_features/generate_printer.py
new file mode 100755
index 0000000..dc56eb5
--- /dev/null
+++ b/cpu_target_features/generate_printer.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python3
+
+"""Generate the compilation target feature printing source code.
+
+The source code for detecting target features is heavily redundant and
+copy-pasted, and is easier to maintain using a generative script.
+
+This script creates the source and the include files in its current
+directory.
+"""
+
+import argparse
+from pathlib import Path
+from typing import Dict, List, Iterable
+
+_CPP_BOILERPLATE: str = """\
+#include <stdio.h>
+
+#define TO_STRING_EXP(DEF) #DEF
+#define TO_STRING(DEF) TO_STRING_EXP(DEF)
+"""
+
+_FEATURES = {
+    "Aarch64": [
+        "__ARM_FEATURE_AES",
+        "__ARM_FEATURE_BTI",
+        "__ARM_FEATURE_CRC32",
+        "__ARM_FEATURE_CRYPTO",
+        "__ARM_FEATURE_PAC_DEFAULT",
+        "__ARM_FEATURE_SHA2",
+        "__ARM_FEATURE_SHA3",
+        "__ARM_FEATURE_SHA512",
+    ],
+    "Arm32": [
+        "__ARM_ARCH_ISA_THUMB",
+        "__ARM_FEATURE_AES",
+        "__ARM_FEATURE_BTI",
+        "__ARM_FEATURE_CRC32",
+        "__ARM_FEATURE_CRYPTO",
+        "__ARM_FEATURE_PAC_DEFAULT",
+        "__ARM_FEATURE_SHA2",
+    ],
+    "X86": [
+        "__AES__",
+        "__AVX__",
+        "__CRC32__",
+        "__POPCNT__",
+        "__SHA512__",
+        "__SHA__",
+    ],
+    "Riscv": [
+        "__riscv_vector",
+    ],
+}
+
+
+def _make_function_sig(name: str) -> str:
+    return f"void print{name}TargetFeatures()"
+
+
+def check_template(define: str) -> List[str]:
+    return [
+        f"#if defined({define})",
+        f'  printf("%s=%s\\n", TO_STRING_EXP({define}), TO_STRING({define}));',
+        "#else",
+        f'  printf("%s not defined\\n", TO_STRING_EXP({define}));',
+        "#endif",
+    ]
+
+
+def generate_cpp_file(define_mapping: Dict[str, List[str]]) -> List[str]:
+    out: List[str] = _CPP_BOILERPLATE.split("\n")
+    for target, defines in define_mapping.items():
+        out.append("")
+        out.extend(generate_print_function(target, defines))
+    return out
+
+
+def generate_print_function(name: str, defines: List[str]) -> List[str]:
+    """Generate a print<DEFINE>TargetFeatures function."""
+    function_body = [_make_function_sig(name) + " {"]
+    for d in defines:
+        function_body.extend(check_template(d))
+    function_body.append("}")
+    return function_body
+
+
+def parse_args() -> argparse.Namespace:
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument(
+        "cpp_in",
+        type=Path,
+        help="Output path to generate the cpp file.",
+    )
+    return parser.parse_args()
+
+
+def main() -> None:
+    args = parse_args()
+    printer_cpp_filepath = args.cpp_in
+    printer_cpp_filepath.write_text(
+        "\n".join(generate_cpp_file(_FEATURES)), encoding="utf-8"
+    )
+
+
+if __name__ == "__main__":
+    main()
diff --git a/cpu_target_features/main.cpp b/cpu_target_features/main.cpp
new file mode 100644
index 0000000..61f3d25
--- /dev/null
+++ b/cpu_target_features/main.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+
+#include "print_target_features.inc"
+
+int main() {
+#if defined(__aarch64__)
+  printAarch64TargetFeatures();
+  return 0;
+#elif defined(__arm__)
+  printArm32TargetFeatures();
+  return 0;
+#elif defined(__x86_64__) || defined(__i386__)
+  printX86TargetFeatures();
+  return 0;
+#elif defined(__riscv)
+  printRiscvTargetFeatures();
+  return 0;
+#else
+#error Unsupported arch. This binary only supports aarch64, arm, x86, x86-64, and risc-v
+#endif
+}
diff --git a/docs/fdtrack.md b/docs/fdtrack.md
index 07c69b3..8928a5c 100644
--- a/docs/fdtrack.md
+++ b/docs/fdtrack.md
@@ -4,9 +4,11 @@
 
 fdtrack is a file descriptor leak checker added to Android in API level 30.
 
-fdtrack consists of two parts: a set of hooks in bionic to register a callback
-that's invoked on file descriptor operations, and a library that implements a
-hook to perform and store backtraces for file descriptor creation.
+fdtrack consists of several parts: a set of hooks in bionic to register a
+callback that's invoked on file descriptor operations, a library that implements
+a hook to perform and store backtraces for file descriptor creation, and
+code in frameworks to automatically enable it (and deliberately crash a process
+that's leaking).
 
 ### bionic hooks
 bionic provides a header in the `bionic_libc_platform_headers` header_lib at <[bionic/fdtrack.h](https://android.googlesource.com/platform/bionic/+/refs/heads/main/libc/platform/bionic/fdtrack.h)>.
@@ -21,6 +23,28 @@
 [libfdtrack](https://android.googlesource.com/platform/bionic/+/refs/heads/main/libfdtrack)
 implements a library that uses libunwindstack to unwind and store fd creation backtraces.
 
+### frameworks
+As the name implies, `spawnFdLeakCheckThread` in SystemServer spawns a thread
+to monitor the number of open file descriptors every so often.
+If that passes a certain threshold, fdtrack is enabled.
+If it passes another threshold, the process is aborted.
+These thresholds are configurable via system properties:
+```
+    // Number of open file descriptors before fdtrack starts; default 1600.
+    private static final String SYSPROP_FDTRACK_ENABLE_THRESHOLD =
+            "persist.sys.debug.fdtrack_enable_threshold";
+
+    // Number of open file descriptors before aborting; default 3000.
+    private static final String SYSPROP_FDTRACK_ABORT_THRESHOLD =
+            "persist.sys.debug.fdtrack_abort_threshold";
+
+    // Number of seconds between open fd count checks; default 120s.
+    private static final String SYSPROP_FDTRACK_INTERVAL =
+            "persist.sys.debug.fdtrack_interval";
+```
+Note that it's also possible to monitor the number of open file descriptors for
+a given process from the shell. `adb shell watch ls -l /proc/<pid>/fd` will show
+them (and you can choose your own update rate as an argument to `watch`).
 
 #### Using libfdtrack
 libfdtrack registers its hook upon being loaded, so to start capturing
diff --git a/libc/Android.bp b/libc/Android.bp
index 39c78b2..3b7ba31 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -1657,7 +1657,6 @@
     },
 
     apex_available: [
-        "//apex_available:platform",
         "com.android.runtime",
     ],
 
@@ -1687,8 +1686,7 @@
     llndk: {
         symbol_file: "libc.map.txt",
         export_headers_as_system: true,
-        export_preprocessed_headers: ["include"],
-        export_llndk_headers: ["libc_llndk_headers"],
+        export_llndk_headers: ["libc_headers"],
     },
 }
 
@@ -1799,7 +1797,7 @@
 }
 
 cc_library_headers {
-    name: "libc_llndk_headers",
+    name: "libc_uapi_headers",
     visibility: [
         "//external/musl",
     ],
@@ -1894,13 +1892,13 @@
     target: {
         android: {
             export_system_include_dirs: ["include"],
-            header_libs: ["libc_llndk_headers"],
-            export_header_lib_headers: ["libc_llndk_headers"],
+            header_libs: ["libc_uapi_headers"],
+            export_header_lib_headers: ["libc_uapi_headers"],
         },
         linux_bionic: {
             export_system_include_dirs: ["include"],
-            header_libs: ["libc_llndk_headers"],
-            export_header_lib_headers: ["libc_llndk_headers"],
+            header_libs: ["libc_uapi_headers"],
+            export_header_lib_headers: ["libc_uapi_headers"],
         },
     },
 }
@@ -2331,17 +2329,6 @@
     name: "libc",
     symbol_file: "libc.map.txt",
     first_version: "9",
-    export_header_libs: [
-        "common_libc",
-        "libc_uapi",
-        "libc_kernel_android_uapi_linux",
-        "libc_kernel_android_scsi",
-        "libc_asm_arm",
-        "libc_asm_arm64",
-        "libc_asm_riscv64",
-        "libc_asm_x86",
-        "libc_asm_x86_64",
-    ],
 }
 
 ndk_library {
diff --git a/libc/bionic/libc_init_static.cpp b/libc/bionic/libc_init_static.cpp
index f8f7d2a..2227856 100644
--- a/libc/bionic/libc_init_static.cpp
+++ b/libc/bionic/libc_init_static.cpp
@@ -473,7 +473,12 @@
 __attribute__((no_sanitize("hwaddress", "memtag"))) __noreturn void __libc_init(
     void* raw_args, void (*onexit)(void) __unused, int (*slingshot)(int, char**, char**),
     structors_array_t const* const structors) {
-  bionic_tcb temp_tcb = {};
+  // We _really_ don't want the compiler to call memset() here,
+  // but it's done so before for riscv64 (http://b/365618934),
+  // so we have to force it to behave.
+  bionic_tcb temp_tcb __attribute__((uninitialized));
+  __builtin_memset_inline(&temp_tcb, 0, sizeof(temp_tcb));
+
 #if __has_feature(hwaddress_sanitizer)
   // Install main thread TLS early. It will be initialized later in __libc_init_main_thread. For now
   // all we need is access to TLS_SLOT_SANITIZER.
@@ -483,6 +488,7 @@
   __hwasan_init_static();
   // We are ready to run HWASan-instrumented code, proceed with libc initialization...
 #endif
+
   __real_libc_init(raw_args, onexit, slingshot, structors, &temp_tcb);
 }
 
diff --git a/libc/bionic/pthread_mutex.cpp b/libc/bionic/pthread_mutex.cpp
index 9b37225..0a452e9 100644
--- a/libc/bionic/pthread_mutex.cpp
+++ b/libc/bionic/pthread_mutex.cpp
@@ -182,7 +182,12 @@
         return 0;
     }
     if (ret == EBUSY) {
-        ScopedTrace trace("Contending for pthread mutex");
+        char trace_msg[64];
+        const pid_t owner = atomic_load_explicit(&mutex.owner_tid, memory_order_relaxed)
+                & FUTEX_TID_MASK;
+        snprintf(trace_msg, sizeof(trace_msg),
+                 "Contending for pthread mutex owned by tid: %d", owner);
+        ScopedTrace trace(trace_msg);
         ret = -__futex_pi_lock_ex(&mutex.owner_tid, mutex.shared, use_realtime_clock, abs_timeout);
     }
     return ret;
diff --git a/libc/include/bits/getentropy.h b/libc/include/bits/getentropy.h
index 4cd44f7..98d8879 100644
--- a/libc/include/bits/getentropy.h
+++ b/libc/include/bits/getentropy.h
@@ -48,6 +48,6 @@
  *
  * See also arc4random_buf() which is available in all API levels.
  */
-int getentropy(void* _Nonnull __buffer, size_t __buffer_size) __wur __INTRODUCED_IN(28);
+__nodiscard int getentropy(void* _Nonnull __buffer, size_t __buffer_size) __INTRODUCED_IN(28);
 
 __END_DECLS
diff --git a/libc/include/malloc.h b/libc/include/malloc.h
index 5904519..2fa4b49 100644
--- a/libc/include/malloc.h
+++ b/libc/include/malloc.h
@@ -55,7 +55,7 @@
  * other processes. Obviously this is not the case for apps, which will
  * be killed in preference to killing other processes.
  */
-void* _Nullable malloc(size_t __byte_count) __mallocfunc __BIONIC_ALLOC_SIZE(1) __wur;
+__nodiscard void* _Nullable malloc(size_t __byte_count) __mallocfunc __BIONIC_ALLOC_SIZE(1);
 
 /**
  * [calloc(3)](https://man7.org/linux/man-pages/man3/calloc.3.html) allocates
@@ -64,7 +64,7 @@
  * Returns a pointer to the allocated memory on success and returns a null
  * pointer and sets `errno` on failure (but see the notes for malloc()).
  */
-void* _Nullable calloc(size_t __item_count, size_t __item_size) __mallocfunc __BIONIC_ALLOC_SIZE(1,2) __wur;
+__nodiscard void* _Nullable calloc(size_t __item_count, size_t __item_size) __mallocfunc __BIONIC_ALLOC_SIZE(1,2);
 
 /**
  * [realloc(3)](https://man7.org/linux/man-pages/man3/realloc.3.html) resizes
@@ -74,11 +74,8 @@
  * memory on success and returns a null pointer and sets `errno` on failure
  * (but see the notes for malloc()).
  */
-void* _Nullable realloc(void* _Nullable __ptr, size_t __byte_count) __BIONIC_ALLOC_SIZE(2) __wur;
+__nodiscard void* _Nullable realloc(void* _Nullable __ptr, size_t __byte_count) __BIONIC_ALLOC_SIZE(2);
 
-// Remove the explicit guard once //external/giflib has been fixed so that it no
-// longer provides a conflicting definition: http://b/352784252
-#if __ANDROID_API__ >= 29
 /**
  * [reallocarray(3)](https://man7.org/linux/man-pages/man3/realloc.3.html) resizes
  * allocated memory on the heap.
@@ -90,7 +87,18 @@
  * memory on success and returns a null pointer and sets `errno` on failure
  * (but see the notes for malloc()).
  */
-void* _Nullable reallocarray(void* _Nullable __ptr, size_t __item_count, size_t __item_size) __BIONIC_ALLOC_SIZE(2, 3) __wur __INTRODUCED_IN(29);
+#if __ANDROID_API__ >= 29
+__nodiscard void* _Nullable reallocarray(void* _Nullable __ptr, size_t __item_count, size_t __item_size) __BIONIC_ALLOC_SIZE(2, 3) __INTRODUCED_IN(29);
+#else
+#include <errno.h>
+static __inline __nodiscard void* _Nullable reallocarray(void* _Nullable __ptr, size_t __item_count, size_t __item_size) {
+  size_t __new_size;
+  if (__builtin_mul_overflow(__item_count, __item_size, &__new_size)) {
+    errno = ENOMEM;
+    return NULL;
+  }
+  return realloc(__ptr, __new_size);
+}
 #endif
 
 /**
@@ -108,13 +116,13 @@
  *
  * See also posix_memalign().
  */
-void* _Nullable memalign(size_t __alignment, size_t __byte_count) __mallocfunc __BIONIC_ALLOC_SIZE(2) __wur;
+__nodiscard void* _Nullable memalign(size_t __alignment, size_t __byte_count) __mallocfunc __BIONIC_ALLOC_SIZE(2);
 
 /**
  * [malloc_usable_size(3)](https://man7.org/linux/man-pages/man3/malloc_usable_size.3.html)
  * returns the actual size of the given heap block.
  */
-size_t malloc_usable_size(const void* _Nullable __ptr) __wur;
+__nodiscard size_t malloc_usable_size(const void* _Nullable __ptr);
 
 #define __MALLINFO_BODY \
   /** Total number of non-mmapped bytes currently allocated from OS. */ \
diff --git a/libc/include/pthread.h b/libc/include/pthread.h
index 33c637f..d718b40 100644
--- a/libc/include/pthread.h
+++ b/libc/include/pthread.h
@@ -142,7 +142,7 @@
                                         const struct timespec* _Nullable __timeout) __INTRODUCED_IN_64(28);
 int pthread_cond_wait(pthread_cond_t* _Nonnull __cond, pthread_mutex_t* _Nonnull __mutex);
 
-int pthread_create(pthread_t* _Nonnull __pthread_ptr, pthread_attr_t const* _Nullable __attr, void* _Nonnull (* _Nonnull __start_routine)(void* _Nonnull), void* _Nullable);
+int pthread_create(pthread_t* _Nonnull __pthread_ptr, pthread_attr_t const* _Nullable __attr, void* _Nullable (* _Nonnull __start_routine)(void* _Nullable), void* _Nullable);
 
 int pthread_detach(pthread_t __pthread);
 void pthread_exit(void* _Nullable __return_value) __noreturn;
diff --git a/libc/include/stdio.h b/libc/include/stdio.h
index d99d032..d24f6af 100644
--- a/libc/include/stdio.h
+++ b/libc/include/stdio.h
@@ -105,10 +105,10 @@
 
 void clearerr(FILE* _Nonnull __fp);
 int fclose(FILE* _Nonnull __fp);
-__wur int feof(FILE* _Nonnull __fp);
-__wur int ferror(FILE* _Nonnull __fp);
+__nodiscard int feof(FILE* _Nonnull __fp);
+__nodiscard int ferror(FILE* _Nonnull __fp);
 int fflush(FILE* _Nullable __fp);
-__wur int fgetc(FILE* _Nonnull __fp);
+__nodiscard int fgetc(FILE* _Nonnull __fp);
 char* _Nullable fgets(char* _Nonnull __buf, int __size, FILE* _Nonnull __fp);
 int fprintf(FILE* _Nonnull __fp , const char* _Nonnull __fmt, ...) __printflike(2, 3);
 int fputc(int __ch, FILE* _Nonnull __fp);
@@ -116,8 +116,8 @@
 size_t fread(void* _Nonnull __buf, size_t __size, size_t __count, FILE* _Nonnull __fp);
 int fscanf(FILE* _Nonnull __fp, const char* _Nonnull __fmt, ...) __scanflike(2, 3);
 size_t fwrite(const void* _Nonnull __buf, size_t __size, size_t __count, FILE* _Nonnull __fp);
-__wur int getc(FILE* _Nonnull __fp);
-__wur int getchar(void);
+__nodiscard int getc(FILE* _Nonnull __fp);
+__nodiscard int getchar(void);
 ssize_t getdelim(char* _Nullable * _Nonnull __line_ptr, size_t* _Nonnull __line_length_ptr, int __delimiter, FILE* _Nonnull __fp);
 ssize_t getline(char* _Nullable * _Nonnull __line_ptr, size_t* _Nonnull __line_length_ptr, FILE* _Nonnull __fp);
 
@@ -201,17 +201,17 @@
 #endif
 
 int fseek(FILE* _Nonnull __fp, long __offset, int __whence);
-__wur long ftell(FILE* _Nonnull __fp);
+__nodiscard long ftell(FILE* _Nonnull __fp);
 
 /* See https://android.googlesource.com/platform/bionic/+/main/docs/32-bit-abi.md */
 #if defined(__USE_FILE_OFFSET64)
 int fgetpos(FILE* _Nonnull __fp, fpos_t* _Nonnull __pos) __RENAME(fgetpos64) __INTRODUCED_IN(24);
 int fsetpos(FILE* _Nonnull __fp, const fpos_t* _Nonnull __pos) __RENAME(fsetpos64) __INTRODUCED_IN(24);
 int fseeko(FILE* _Nonnull __fp, off_t __offset, int __whence) __RENAME(fseeko64) __INTRODUCED_IN(24);
-__wur off_t ftello(FILE* _Nonnull __fp) __RENAME(ftello64) __INTRODUCED_IN(24);
+__nodiscard off_t ftello(FILE* _Nonnull __fp) __RENAME(ftello64) __INTRODUCED_IN(24);
 #  if defined(__USE_BSD)
 /* If __read_fn and __write_fn are both nullptr, it will cause EINVAL */
-__wur FILE* _Nullable funopen(const void* _Nullable __cookie,
+__nodiscard FILE* _Nullable funopen(const void* _Nullable __cookie,
               int (* __BIONIC_COMPLICATED_NULLNESS __read_fn)(void* _Nonnull, char* _Nonnull, int),
               int (* __BIONIC_COMPLICATED_NULLNESS __write_fn)(void* _Nonnull, const char* _Nonnull, int),
               fpos_t (* _Nullable __seek_fn)(void* _Nonnull, fpos_t, int),
@@ -221,10 +221,10 @@
 int fgetpos(FILE* _Nonnull __fp, fpos_t* _Nonnull __pos);
 int fsetpos(FILE* _Nonnull __fp, const fpos_t* _Nonnull __pos);
 int fseeko(FILE* _Nonnull __fp, off_t __offset, int __whence);
-__wur off_t ftello(FILE* _Nonnull __fp);
+__nodiscard off_t ftello(FILE* _Nonnull __fp);
 #  if defined(__USE_BSD)
 /* If __read_fn and __write_fn are both nullptr, it will cause EINVAL */
-__wur FILE* _Nullable funopen(const void* _Nullable __cookie,
+__nodiscard FILE* _Nullable funopen(const void* _Nullable __cookie,
               int (* __BIONIC_COMPLICATED_NULLNESS __read_fn)(void* _Nonnull, char* _Nonnull, int),
               int (* __BIONIC_COMPLICATED_NULLNESS __write_fn)(void* _Nonnull, const char* _Nonnull, int),
               fpos_t (* _Nullable __seek_fn)(void* _Nonnull, fpos_t, int),
@@ -234,22 +234,22 @@
 int fgetpos64(FILE* _Nonnull __fp, fpos64_t* _Nonnull __pos) __INTRODUCED_IN(24);
 int fsetpos64(FILE* _Nonnull __fp, const fpos64_t* _Nonnull __pos) __INTRODUCED_IN(24);
 int fseeko64(FILE* _Nonnull __fp, off64_t __offset, int __whence) __INTRODUCED_IN(24);
-__wur off64_t ftello64(FILE* _Nonnull __fp) __INTRODUCED_IN(24);
+__nodiscard off64_t ftello64(FILE* _Nonnull __fp) __INTRODUCED_IN(24);
 #if defined(__USE_BSD)
 /* If __read_fn and __write_fn are both nullptr, it will cause EINVAL */
-__wur FILE* _Nullable funopen64(const void* _Nullable __cookie,
+__nodiscard FILE* _Nullable funopen64(const void* _Nullable __cookie,
                 int (* __BIONIC_COMPLICATED_NULLNESS __read_fn)(void* _Nonnull, char* _Nonnull, int),
                 int (* __BIONIC_COMPLICATED_NULLNESS __write_fn)(void* _Nonnull, const char* _Nonnull, int),
                 fpos64_t (* _Nullable __seek_fn)(void* _Nonnull, fpos64_t, int),
                 int (* _Nullable __close_fn)(void* _Nonnull)) __INTRODUCED_IN(24);
 #endif
 
-__wur FILE* _Nullable fopen(const char* _Nonnull __path, const char* _Nonnull __mode);
-__wur FILE* _Nullable fopen64(const char* _Nonnull __path, const char* _Nonnull __mode) __INTRODUCED_IN(24);
+__nodiscard FILE* _Nullable fopen(const char* _Nonnull __path, const char* _Nonnull __mode);
+__nodiscard FILE* _Nullable fopen64(const char* _Nonnull __path, const char* _Nonnull __mode) __INTRODUCED_IN(24);
 FILE* _Nullable freopen(const char* _Nullable __path, const char* _Nonnull __mode, FILE* _Nonnull __fp);
 FILE* _Nullable freopen64(const char* _Nullable __path, const char* _Nonnull __mode, FILE* _Nonnull __fp) __INTRODUCED_IN(24);
-__wur FILE* _Nullable tmpfile(void);
-__wur FILE* _Nullable tmpfile64(void) __INTRODUCED_IN(24);
+__nodiscard FILE* _Nullable tmpfile(void);
+__nodiscard FILE* _Nullable tmpfile64(void) __INTRODUCED_IN(24);
 
 int snprintf(char* __BIONIC_COMPLICATED_NULLNESS __buf, size_t __size, const char* _Nonnull __fmt, ...) __printflike(3, 4);
 int vfscanf(FILE* _Nonnull __fp, const char* _Nonnull __fmt, va_list __args) __scanflike(2, 0);
@@ -260,20 +260,20 @@
 #define L_ctermid 1024 /* size for ctermid() */
 char* _Nonnull ctermid(char* _Nullable __buf) __INTRODUCED_IN(26);
 
-__wur FILE* _Nullable fdopen(int __fd, const char* _Nonnull __mode);
-__wur int fileno(FILE* _Nonnull __fp);
+__nodiscard FILE* _Nullable fdopen(int __fd, const char* _Nonnull __mode);
+__nodiscard int fileno(FILE* _Nonnull __fp);
 int pclose(FILE* _Nonnull __fp);
-__wur FILE* _Nullable popen(const char* _Nonnull __command, const char* _Nonnull __mode);
+__nodiscard FILE* _Nullable popen(const char* _Nonnull __command, const char* _Nonnull __mode);
 void flockfile(FILE* _Nonnull  __fp);
 int ftrylockfile(FILE* _Nonnull __fp);
 void funlockfile(FILE* _Nonnull __fp);
-__wur int getc_unlocked(FILE* _Nonnull __fp);
-__wur int getchar_unlocked(void);
+__nodiscard int getc_unlocked(FILE* _Nonnull __fp);
+__nodiscard int getchar_unlocked(void);
 int putc_unlocked(int __ch, FILE* _Nonnull __fp);
 int putchar_unlocked(int __ch);
 
-__wur FILE* _Nullable fmemopen(void* _Nullable __buf, size_t __size, const char* _Nonnull __mode) __INTRODUCED_IN(23);
-__wur FILE* _Nullable open_memstream(char* _Nonnull * _Nonnull __ptr, size_t* _Nonnull __size_ptr) __INTRODUCED_IN(23);
+__nodiscard FILE* _Nullable fmemopen(void* _Nullable __buf, size_t __size, const char* _Nonnull __mode) __INTRODUCED_IN(23);
+__nodiscard FILE* _Nullable open_memstream(char* _Nonnull * _Nonnull __ptr, size_t* _Nonnull __size_ptr) __INTRODUCED_IN(23);
 
 #if defined(__USE_BSD) || defined(__BIONIC__) /* Historically bionic exposed these. */
 int  asprintf(char* _Nullable * _Nonnull __s_ptr, const char* _Nonnull __fmt, ...) __printflike(2, 3);
@@ -283,16 +283,16 @@
 int setlinebuf(FILE* _Nonnull __fp);
 int vasprintf(char* _Nullable * _Nonnull __s_ptr, const char* _Nonnull __fmt, va_list __args) __printflike(2, 0);
 void clearerr_unlocked(FILE* _Nonnull __fp) __INTRODUCED_IN(23);
-__wur int feof_unlocked(FILE* _Nonnull __fp) __INTRODUCED_IN(23);
-__wur int ferror_unlocked(FILE* _Nonnull __fp) __INTRODUCED_IN(23);
-__wur int fileno_unlocked(FILE* _Nonnull __fp) __INTRODUCED_IN(24);
+__nodiscard int feof_unlocked(FILE* _Nonnull __fp) __INTRODUCED_IN(23);
+__nodiscard int ferror_unlocked(FILE* _Nonnull __fp) __INTRODUCED_IN(23);
+__nodiscard int fileno_unlocked(FILE* _Nonnull __fp) __INTRODUCED_IN(24);
 #define fropen(cookie, fn) funopen(cookie, fn, 0, 0, 0)
 #define fwopen(cookie, fn) funopen(cookie, 0, fn, 0, 0)
 #endif
 
 #if defined(__USE_BSD)
 int fflush_unlocked(FILE* _Nullable __fp) __INTRODUCED_IN(28);
-__wur int fgetc_unlocked(FILE* _Nonnull __fp) __INTRODUCED_IN(28);
+__nodiscard int fgetc_unlocked(FILE* _Nonnull __fp) __INTRODUCED_IN(28);
 int fputc_unlocked(int __ch, FILE* _Nonnull __fp) __INTRODUCED_IN(28);
 size_t fread_unlocked(void* _Nonnull __buf, size_t __size, size_t __count, FILE* _Nonnull __fp) __INTRODUCED_IN(28);
 size_t fwrite_unlocked(const void* _Nonnull __buf, size_t __size, size_t __count, FILE* _Nonnull __fp) __INTRODUCED_IN(28);
diff --git a/libc/include/stdlib.h b/libc/include/stdlib.h
index b31b122..076a978 100644
--- a/libc/include/stdlib.h
+++ b/libc/include/stdlib.h
@@ -79,9 +79,9 @@
  *
  * Available since API level 28.
  */
-__wur void* _Nullable aligned_alloc(size_t __alignment, size_t __size) __INTRODUCED_IN(28);
+__nodiscard void* _Nullable aligned_alloc(size_t __alignment, size_t __size) __INTRODUCED_IN(28);
 
-__wur char* _Nullable realpath(const char* _Nonnull __path, char* _Nullable __resolved);
+__nodiscard char* _Nullable realpath(const char* _Nonnull __path, char* _Nullable __resolved);
 
 /**
  * [system(3)](https://man7.org/linux/man-pages/man3/system.3.html) executes
@@ -107,7 +107,7 @@
  * Returns a pointer to a matching item on success,
  * or NULL if no matching item is found.
  */
-__wur void* _Nullable bsearch(const void* _Nonnull __key, const void* _Nullable __base, size_t __nmemb, size_t __size, int (* _Nonnull __comparator)(const void* _Nonnull __lhs, const void* _Nonnull __rhs));
+__nodiscard void* _Nullable bsearch(const void* _Nonnull __key, const void* _Nullable __base, size_t __nmemb, size_t __size, int (* _Nonnull __comparator)(const void* _Nonnull __lhs, const void* _Nonnull __rhs));
 
 /**
  * [qsort(3)](https://man7.org/linux/man-pages/man3/qsort.3.html) sorts an array
diff --git a/libc/include/sys/cdefs.h b/libc/include/sys/cdefs.h
index 5d1718e..4aea97a 100644
--- a/libc/include/sys/cdefs.h
+++ b/libc/include/sys/cdefs.h
@@ -140,7 +140,8 @@
 #define	__predict_true(exp)	__builtin_expect((exp) != 0, 1)
 #define	__predict_false(exp)	__builtin_expect((exp) != 0, 0)
 
-#define __wur __attribute__((__warn_unused_result__))
+#define __nodiscard __attribute__((__warn_unused_result__))
+#define __wur __nodiscard
 
 #define __errorattr(msg) __attribute__((__unavailable__(msg)))
 #define __warnattr(msg) __attribute__((__deprecated__(msg)))
diff --git a/libc/include/sys/eventfd.h b/libc/include/sys/eventfd.h
index b9d3fe9..3000737 100644
--- a/libc/include/sys/eventfd.h
+++ b/libc/include/sys/eventfd.h
@@ -35,15 +35,19 @@
 
 #include <sys/cdefs.h>
 #include <fcntl.h>
+#include <linux/eventfd.h>
 
 __BEGIN_DECLS
 
-/** The eventfd() flag to provide semaphore-like semantics for reads. */
-#define EFD_SEMAPHORE (1 << 0)
-/** The eventfd() flag for a close-on-exec file descriptor. */
-#define EFD_CLOEXEC O_CLOEXEC
-/** The eventfd() flag for a non-blocking file descriptor. */
-#define EFD_NONBLOCK O_NONBLOCK
+/*! \macro EFD_SEMAPHORE
+ * The eventfd() flag to provide semaphore-like semantics for reads.
+ */
+/*! \macro EFD_CLOEXEC
+ * The eventfd() flag for a close-on-exec file descriptor.
+ */
+/*! \macro EFD_NONBLOCK
+ * The eventfd() flag for a non-blocking file descriptor.
+ */
 
 /**
  * [eventfd(2)](https://man7.org/linux/man-pages/man2/eventfd.2.html) creates a file descriptor
diff --git a/libc/include/sys/inotify.h b/libc/include/sys/inotify.h
index f070857..75ed542 100644
--- a/libc/include/sys/inotify.h
+++ b/libc/include/sys/inotify.h
@@ -33,13 +33,9 @@
 #include <sys/types.h>
 #include <stdint.h>
 #include <linux/inotify.h>
-#include <asm/fcntl.h> /* For O_CLOEXEC and O_NONBLOCK. */
 
 __BEGIN_DECLS
 
-#define IN_CLOEXEC O_CLOEXEC
-#define IN_NONBLOCK O_NONBLOCK
-
 int inotify_init(void);
 int inotify_init1(int __flags);
 int inotify_add_watch(int __fd, const char* _Nonnull __path, uint32_t __mask);
diff --git a/libc/include/sys/random.h b/libc/include/sys/random.h
index fcea419..b4a9993 100644
--- a/libc/include/sys/random.h
+++ b/libc/include/sys/random.h
@@ -52,6 +52,6 @@
  *
  * See also arc4random_buf() which is available in all API levels.
  */
-ssize_t getrandom(void* _Nonnull __buffer, size_t __buffer_size, unsigned int __flags) __wur __INTRODUCED_IN(28);
+__nodiscard ssize_t getrandom(void* _Nonnull __buffer, size_t __buffer_size, unsigned int __flags) __INTRODUCED_IN(28);
 
 __END_DECLS
diff --git a/libc/include/sys/timerfd.h b/libc/include/sys/timerfd.h
index 96d3bef..bfa9a55 100644
--- a/libc/include/sys/timerfd.h
+++ b/libc/include/sys/timerfd.h
@@ -33,17 +33,20 @@
  * @brief Timer file descriptors.
  */
 
-#include <fcntl.h> /* For O_CLOEXEC and O_NONBLOCK. */
+#include <fcntl.h>
+#include <linux/timerfd.h>
 #include <time.h>
 #include <sys/cdefs.h>
 #include <sys/types.h>
 
 __BEGIN_DECLS
 
-/** The timerfd_create() flag for a close-on-exec file descriptor. */
-#define TFD_CLOEXEC O_CLOEXEC
-/** The timerfd_create() flag for a non-blocking file descriptor. */
-#define TFD_NONBLOCK O_NONBLOCK
+/*! \macro TFD_CLOEXEC
+ * The timerfd_create() flag for a close-on-exec file descriptor.
+ */
+/*! \macro TFD_NONBLOCK
+ * The timerfd_create() flag for a non-blocking file descriptor.
+ */
 
 /**
  * [timerfd_create(2)](https://man7.org/linux/man-pages/man2/timerfd_create.2.html) creates a
diff --git a/libc/include/sys/vfs.h b/libc/include/sys/vfs.h
index 3579799..1a640ba 100644
--- a/libc/include/sys/vfs.h
+++ b/libc/include/sys/vfs.h
@@ -40,6 +40,8 @@
 typedef __fsid_t fsid_t;
 
 #if defined(__LP64__)
+/* We can't just use the kernel struct statfs directly here because
+ * it's reused for both struct statfs *and* struct statfs64. */
 #define __STATFS64_BODY \
   uint64_t f_type; \
   uint64_t f_bsize; \
diff --git a/libc/include/threads.h b/libc/include/threads.h
index b1008de..1074fa4 100644
--- a/libc/include/threads.h
+++ b/libc/include/threads.h
@@ -72,8 +72,10 @@
   thrd_timedout = 4,
 };
 
-#if !defined(__cplusplus)
-#define thread_local _Thread_local
+/* `thread_local` is a keyword in C++11 and C23; C11 had `_Thread_local` instead. */
+#if !defined(__cplusplus) && (__STDC_VERSION__ >= 201112L && __STDC_VERSION__ < 202311L)
+# undef thread_local
+# define thread_local _Thread_local
 #endif
 
 __BEGIN_DECLS
diff --git a/libc/private/bionic_mbstate.h b/libc/private/bionic_mbstate.h
index 0e5f861..fb85775 100644
--- a/libc/private/bionic_mbstate.h
+++ b/libc/private/bionic_mbstate.h
@@ -38,11 +38,11 @@
   (rv == BIONIC_MULTIBYTE_RESULT_ILLEGAL_SEQUENCE || \
    rv == BIONIC_MULTIBYTE_RESULT_INCOMPLETE_SEQUENCE)
 
-static inline __wur bool mbstate_is_initial(const mbstate_t* ps) {
+static inline __nodiscard bool mbstate_is_initial(const mbstate_t* ps) {
   return *(reinterpret_cast<const uint32_t*>(ps->__seq)) == 0;
 }
 
-static inline __wur size_t mbstate_bytes_so_far(const mbstate_t* ps) {
+static inline __nodiscard size_t mbstate_bytes_so_far(const mbstate_t* ps) {
   return
       (ps->__seq[2] != 0) ? 3 :
       (ps->__seq[1] != 0) ? 2 :
@@ -53,7 +53,7 @@
   ps->__seq[i] = static_cast<uint8_t>(byte);
 }
 
-static inline __wur uint8_t mbstate_get_byte(const mbstate_t* ps, int n) {
+static inline __nodiscard uint8_t mbstate_get_byte(const mbstate_t* ps, int n) {
   return ps->__seq[n];
 }
 
@@ -61,13 +61,13 @@
   *(reinterpret_cast<uint32_t*>(ps->__seq)) = 0;
 }
 
-static inline __wur size_t mbstate_reset_and_return_illegal(int _errno, mbstate_t* ps) {
+static inline __nodiscard size_t mbstate_reset_and_return_illegal(int _errno, mbstate_t* ps) {
   errno = _errno;
   mbstate_reset(ps);
   return BIONIC_MULTIBYTE_RESULT_ILLEGAL_SEQUENCE;
 }
 
-static inline __wur size_t mbstate_reset_and_return(size_t _return, mbstate_t* ps) {
+static inline __nodiscard size_t mbstate_reset_and_return(size_t _return, mbstate_t* ps) {
   mbstate_reset(ps);
   return _return;
 }
diff --git a/libdl/Android.bp b/libdl/Android.bp
index 1bbd902..87db4b1 100644
--- a/libdl/Android.bp
+++ b/libdl/Android.bp
@@ -123,7 +123,6 @@
     },
 
     apex_available: [
-        "//apex_available:platform",
         "com.android.runtime",
     ],
 }
@@ -170,7 +169,6 @@
     },
 
     apex_available: [
-        "//apex_available:platform",
         "com.android.runtime",
     ],
 }
diff --git a/libm/Android.bp b/libm/Android.bp
index 9fd79f8..ee86959 100644
--- a/libm/Android.bp
+++ b/libm/Android.bp
@@ -435,7 +435,6 @@
     },
 
     apex_available: [
-        "//apex_available:platform",
         "com.android.runtime",
     ],
 
diff --git a/libm/upstream-freebsd/lib/msun/src/e_acosf.c b/libm/upstream-freebsd/lib/msun/src/e_acosf.c
index 42ba126..ede552e 100644
--- a/libm/upstream-freebsd/lib/msun/src/e_acosf.c
+++ b/libm/upstream-freebsd/lib/msun/src/e_acosf.c
@@ -22,11 +22,17 @@
 pio2_hi =  1.5707962513e+00; /* 0x3fc90fda */
 static volatile float
 pio2_lo =  7.5497894159e-08; /* 0x33a22168 */
+
+/*
+ * The coefficients for the rational approximation were generated over
+ *  0x1p-12f <= x <= 0.5f.  The maximum error satisfies log2(e) < -30.084.
+ */
 static const float
-pS0 =  1.6666586697e-01,
-pS1 = -4.2743422091e-02,
-pS2 = -8.6563630030e-03,
-qS1 = -7.0662963390e-01;
+pS0 =  1.66666672e-01f, /* 0x3e2aaaab */
+pS1 = -1.19510300e-01f, /* 0xbdf4c1d1 */
+pS2 =  5.47002675e-03f, /* 0x3bb33de9 */
+qS1 = -1.16706085e+00f, /* 0xbf956240 */
+qS2 =  2.90115148e-01f; /* 0x3e9489f9 */
 
 float
 acosf(float x)
@@ -46,13 +52,13 @@
 	    if(ix<=0x32800000) return pio2_hi+pio2_lo;/*if|x|<2**-26*/
 	    z = x*x;
 	    p = z*(pS0+z*(pS1+z*pS2));
-	    q = one+z*qS1;
+	    q = one+z*(qS1+z*qS2);
 	    r = p/q;
 	    return pio2_hi - (x - (pio2_lo-x*r));
 	} else  if (hx<0) {		/* x < -0.5 */
 	    z = (one+x)*(float)0.5;
 	    p = z*(pS0+z*(pS1+z*pS2));
-	    q = one+z*qS1;
+	    q = one+z*(qS1+z*qS2);
 	    s = sqrtf(z);
 	    r = p/q;
 	    w = r*s-pio2_lo;
@@ -66,7 +72,7 @@
 	    SET_FLOAT_WORD(df,idf&0xfffff000);
 	    c  = (z-df*df)/(s+df);
 	    p = z*(pS0+z*(pS1+z*pS2));
-	    q = one+z*qS1;
+	    q = one+z*(qS1+z*qS2);
 	    r = p/q;
 	    w = r*s+c;
 	    return (float)2.0*(df+w);
diff --git a/libm/upstream-freebsd/lib/msun/src/e_asinf.c b/libm/upstream-freebsd/lib/msun/src/e_asinf.c
index a2ee1a1..8d1aca2 100644
--- a/libm/upstream-freebsd/lib/msun/src/e_asinf.c
+++ b/libm/upstream-freebsd/lib/msun/src/e_asinf.c
@@ -18,12 +18,18 @@
 
 static const float
 one =  1.0000000000e+00, /* 0x3F800000 */
-huge =  1.000e+30,
-	/* coefficient for R(x^2) */
-pS0 =  1.6666586697e-01,
-pS1 = -4.2743422091e-02,
-pS2 = -8.6563630030e-03,
-qS1 = -7.0662963390e-01;
+huge =  1.000e+30;
+
+/*
+ * The coefficients for the rational approximation were generated over
+ *  0x1p-12f <= x <= 0.5f.  The maximum error satisfies log2(e) < -30.084.
+ */
+static const float
+pS0 =  1.66666672e-01f, /* 0x3e2aaaab */
+pS1 = -1.19510300e-01f, /* 0xbdf4c1d1 */
+pS2 =  5.47002675e-03f, /* 0x3bb33de9 */
+qS1 = -1.16706085e+00f, /* 0xbf956240 */
+qS2 =  2.90115148e-01f; /* 0x3e9489f9 */
 
 static const double
 pio2 =  1.570796326794896558e+00;
@@ -46,7 +52,7 @@
 	    }
 	    t = x*x;
 	    p = t*(pS0+t*(pS1+t*pS2));
-	    q = one+t*qS1;
+	    q = one+t*(qS1+t*qS2);
 	    w = p/q;
 	    return x+x*w;
 	}
@@ -54,7 +60,7 @@
 	w = one-fabsf(x);
 	t = w*(float)0.5;
 	p = t*(pS0+t*(pS1+t*pS2));
-	q = one+t*qS1;
+	q = one+t*(qS1+t*qS2);
 	s = sqrt(t);
 	w = p/q;
 	t = pio2-2.0*(s+s*w);
diff --git a/libm/upstream-freebsd/lib/msun/src/math_private.h b/libm/upstream-freebsd/lib/msun/src/math_private.h
index f3f7985..1595f90 100644
--- a/libm/upstream-freebsd/lib/msun/src/math_private.h
+++ b/libm/upstream-freebsd/lib/msun/src/math_private.h
@@ -405,7 +405,7 @@
  * any extra precision into the type of 'a' -- 'a' should have type float_t,
  * double_t or long double.  b's type should be no larger than 'a's type.
  * Callers should use these types with scopes as large as possible, to
- * reduce their own extra-precision and efficiciency problems.  In
+ * reduce their own extra-precision and efficiency problems.  In
  * particular, they shouldn't convert back and forth just to call here.
  */
 #ifdef DEBUG
diff --git a/libm/upstream-freebsd/lib/msun/src/s_fma.c b/libm/upstream-freebsd/lib/msun/src/s_fma.c
index 6c889a6..23a8449 100644
--- a/libm/upstream-freebsd/lib/msun/src/s_fma.c
+++ b/libm/upstream-freebsd/lib/msun/src/s_fma.c
@@ -260,14 +260,14 @@
 
 	spread = ex + ey;
 
-	if (r.hi == 0.0) {
+	if (r.hi == 0.0 && xy.lo == 0) {
 		/*
 		 * When the addends cancel to 0, ensure that the result has
 		 * the correct sign.
 		 */
 		fesetround(oround);
 		volatile double vzs = zs; /* XXX gcc CSE bug workaround */
-		return (xy.hi + vzs + ldexp(xy.lo, spread));
+		return (xy.hi + vzs);
 	}
 
 	if (oround != FE_TONEAREST) {
diff --git a/libm/upstream-freebsd/lib/msun/src/s_fmal.c b/libm/upstream-freebsd/lib/msun/src/s_fmal.c
index 80c835d..2fca206 100644
--- a/libm/upstream-freebsd/lib/msun/src/s_fmal.c
+++ b/libm/upstream-freebsd/lib/msun/src/s_fmal.c
@@ -241,14 +241,14 @@
 
 	spread = ex + ey;
 
-	if (r.hi == 0.0) {
+	if (r.hi == 0.0 && xy.lo == 0) {
 		/*
 		 * When the addends cancel to 0, ensure that the result has
 		 * the correct sign.
 		 */
 		fesetround(oround);
 		volatile long double vzs = zs; /* XXX gcc CSE bug workaround */
-		return (xy.hi + vzs + ldexpl(xy.lo, spread));
+		return (xy.hi + vzs);
 	}
 
 	if (oround != FE_TONEAREST) {
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 4365ea5..bcc2500 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -316,7 +316,7 @@
     async_safe_fatal("library name \"%s\" too long", name);
   }
 
-  TRACE("name %s: allocating soinfo for ns=%p", name, ns);
+  LD_DEBUG(any, "name %s: allocating soinfo for ns=%p", name, ns);
 
   soinfo* si = new (g_soinfo_allocator.alloc()) soinfo(ns, name, file_stat,
                                                        file_offset, rtld_flags);
@@ -326,7 +326,7 @@
   si->generate_handle();
   ns->add_soinfo(si);
 
-  TRACE("name %s: allocated soinfo @ %p", name, si);
+  LD_DEBUG(any, "name %s: allocated soinfo @ %p", name, si);
   return si;
 }
 
@@ -349,7 +349,7 @@
     munmap(reinterpret_cast<void*>(si->get_gap_start()), si->get_gap_size());
   }
 
-  TRACE("name %s: freeing soinfo @ %p", si->get_realpath(), si);
+  LD_DEBUG(any, "name %s: freeing soinfo @ %p", si->get_realpath(), si);
 
   if (!solist_remove_soinfo(si)) {
     async_safe_fatal("soinfo=%p is not in soinfo_list (double unload?)", si);
@@ -387,7 +387,7 @@
   auto length = readlink(proc_self_fd, buf, sizeof(buf));
   if (length == -1) {
     if (!is_first_stage_init()) {
-      PRINT("readlink(\"%s\" [fd=%d]) failed: %m", proc_self_fd, fd);
+      DL_WARN("readlink(\"%s\" [fd=%d]) failed: %m", proc_self_fd, fd);
     }
     return false;
   }
@@ -818,8 +818,8 @@
   }
 
   if (s != nullptr) {
-    TRACE_TYPE(LOOKUP, "%s s->st_value = %p, found->base = %p",
-               name, reinterpret_cast<void*>(s->st_value), reinterpret_cast<void*>((*found)->base));
+    LD_DEBUG(lookup, "%s s->st_value = %p, found->base = %p",
+             name, reinterpret_cast<void*>(s->st_value), reinterpret_cast<void*>((*found)->base));
   }
 
   return s;
@@ -923,7 +923,7 @@
   }
 
   const char* const path = normalized_path.c_str();
-  TRACE("Trying zip file open from path \"%s\" -> normalized \"%s\"", input_path, path);
+  LD_DEBUG(any, "Trying zip file open from path \"%s\" -> normalized \"%s\"", input_path, path);
 
   // Treat an '!/' separator inside a path as the separator between the name
   // of the zip file on disk and the subdirectory to search within it.
@@ -936,7 +936,7 @@
 
   char buf[512];
   if (strlcpy(buf, path, sizeof(buf)) >= sizeof(buf)) {
-    PRINT("Warning: ignoring very long library path: %s", path);
+    DL_WARN("ignoring very long library path: %s", path);
     return -1;
   }
 
@@ -976,8 +976,8 @@
     *realpath += separator;
   } else {
     if (!is_first_stage_init()) {
-      PRINT("warning: unable to get realpath for the library \"%s\". Will use given path.",
-            normalized_path.c_str());
+      DL_WARN("unable to get realpath for the library \"%s\". Will use given path.",
+              normalized_path.c_str());
     }
     *realpath = normalized_path;
   }
@@ -988,7 +988,7 @@
 static bool format_path(char* buf, size_t buf_size, const char* path, const char* name) {
   int n = async_safe_format_buffer(buf, buf_size, "%s/%s", path, name);
   if (n < 0 || n >= static_cast<int>(buf_size)) {
-    PRINT("Warning: ignoring very long library path: %s/%s", path, name);
+    DL_WARN("ignoring very long library path: %s/%s", path, name);
     return false;
   }
 
@@ -1009,8 +1009,7 @@
       *file_offset = 0;
       if (!realpath_fd(fd, realpath)) {
         if (!is_first_stage_init()) {
-          PRINT("warning: unable to get realpath for the library \"%s\". Will use given path.",
-                path);
+          DL_WARN("unable to get realpath for the library \"%s\". Will use given path.", path);
         }
         *realpath = path;
       }
@@ -1043,7 +1042,7 @@
                         ZipArchiveCache* zip_archive_cache,
                         const char* name, soinfo *needed_by,
                         off64_t* file_offset, std::string* realpath) {
-  TRACE("[ opening %s from namespace %s ]", name, ns->get_name());
+  LD_DEBUG(any, "[ opening %s from namespace %s ]", name, ns->get_name());
 
   // If the name contains a slash, we should attempt to open it directly and not search the paths.
   if (strchr(name, '/') != nullptr) {
@@ -1249,15 +1248,15 @@
 
       // do not print this if a library is in the list of shared libraries for linked namespaces
       if (!maybe_accessible_via_namespace_links(ns, name)) {
-        PRINT("library \"%s\" (\"%s\") needed or dlopened by \"%s\" is not accessible for the"
-              " namespace: [name=\"%s\", ld_library_paths=\"%s\", default_library_paths=\"%s\","
-              " permitted_paths=\"%s\"]",
-              name, realpath.c_str(),
-              needed_or_dlopened_by,
-              ns->get_name(),
-              android::base::Join(ns->get_ld_library_paths(), ':').c_str(),
-              android::base::Join(ns->get_default_library_paths(), ':').c_str(),
-              android::base::Join(ns->get_permitted_paths(), ':').c_str());
+        DL_WARN("library \"%s\" (\"%s\") needed or dlopened by \"%s\" is not accessible for the"
+                " namespace: [name=\"%s\", ld_library_paths=\"%s\", default_library_paths=\"%s\","
+                " permitted_paths=\"%s\"]",
+                name, realpath.c_str(),
+                needed_or_dlopened_by,
+                ns->get_name(),
+                android::base::Join(ns->get_ld_library_paths(), ':').c_str(),
+                android::base::Join(ns->get_default_library_paths(), ':').c_str(),
+                android::base::Join(ns->get_permitted_paths(), ':').c_str());
       }
       return false;
     }
@@ -1330,10 +1329,9 @@
     std::string realpath;
     if (!realpath_fd(extinfo->library_fd, &realpath)) {
       if (!is_first_stage_init()) {
-        PRINT(
-            "warning: unable to get realpath for the library \"%s\" by extinfo->library_fd. "
-            "Will use given name.",
-            name);
+        DL_WARN("unable to get realpath for the library \"%s\" by extinfo->library_fd. "
+                "Will use given name.",
+                name);
       }
       realpath = name;
     }
@@ -1474,8 +1472,8 @@
 
   // Library might still be loaded, the accurate detection
   // of this fact is done by load_library.
-  TRACE("[ \"%s\" find_loaded_library_by_soname failed (*candidate=%s@%p). Trying harder... ]",
-        task->get_name(), candidate == nullptr ? "n/a" : candidate->get_realpath(), candidate);
+  LD_DEBUG(any, "[ \"%s\" find_loaded_library_by_soname failed (*candidate=%s@%p). Trying harder... ]",
+           task->get_name(), candidate == nullptr ? "n/a" : candidate->get_realpath(), candidate);
 
   if (load_library(ns, task, zip_archive_cache, load_tasks, rtld_flags,
                    true /* search_linked_namespaces */)) {
@@ -1906,8 +1904,8 @@
     if (si->has_min_version(0)) {
       soinfo* child = nullptr;
       while ((child = si->get_children().pop_front()) != nullptr) {
-        TRACE("%s@%p needs to unload %s@%p", si->get_realpath(), si,
-            child->get_realpath(), child);
+        LD_DEBUG(any, "%s@%p needs to unload %s@%p", si->get_realpath(), si,
+                 child->get_realpath(), child);
 
         child->get_parents().remove(si);
 
@@ -2197,10 +2195,10 @@
       if (file_exists(translated_name_holder.c_str())) {
         soinfo* si = nullptr;
         if (find_loaded_library_by_realpath(ns, original_path, true, &si)) {
-          PRINT("linker_asan dlopen NOT translating \"%s\" -> \"%s\": library already loaded", name,
-                translated_name_holder.c_str());
+          DL_WARN("linker_asan dlopen NOT translating \"%s\" -> \"%s\": library already loaded", name,
+                  translated_name_holder.c_str());
         } else {
-          PRINT("linker_asan dlopen translating \"%s\" -> \"%s\"", name, translated_name);
+          DL_WARN("linker_asan dlopen translating \"%s\" -> \"%s\"", name, translated_name);
           translated_name = translated_name_holder.c_str();
         }
       }
@@ -2217,10 +2215,10 @@
       if (!translated_name_holder.empty() && file_exists(translated_name_holder.c_str())) {
         soinfo* si = nullptr;
         if (find_loaded_library_by_realpath(ns, original_path, true, &si)) {
-          PRINT("linker_hwasan dlopen NOT translating \"%s\" -> \"%s\": library already loaded", name,
-                translated_name_holder.c_str());
+          DL_WARN("linker_hwasan dlopen NOT translating \"%s\" -> \"%s\": library already loaded",
+                  name, translated_name_holder.c_str());
         } else {
-          PRINT("linker_hwasan dlopen translating \"%s\" -> \"%s\"", name, translated_name);
+          DL_WARN("linker_hwasan dlopen translating \"%s\" -> \"%s\"", name, translated_name);
           translated_name = translated_name_holder.c_str();
         }
       }
@@ -2589,8 +2587,8 @@
   if (g_is_ldd) return 0;
 
   ElfW(Addr) ifunc_addr = __bionic_call_ifunc_resolver(resolver_addr);
-  TRACE_TYPE(RELO, "Called ifunc_resolver@%p. The result is %p",
-      reinterpret_cast<void *>(resolver_addr), reinterpret_cast<void*>(ifunc_addr));
+  LD_DEBUG(calls, "ifunc_resolver@%p returned %p",
+           reinterpret_cast<void *>(resolver_addr), reinterpret_cast<void*>(ifunc_addr));
 
   return ifunc_addr;
 }
@@ -2842,8 +2840,8 @@
   /* We can't log anything until the linker is relocated */
   bool relocating_linker = (flags_ & FLAG_LINKER) != 0;
   if (!relocating_linker) {
-    INFO("[ Linking \"%s\" ]", get_realpath());
-    DEBUG("si->base = %p si->flags = 0x%08x", reinterpret_cast<void*>(base), flags_);
+    LD_DEBUG(any, "[ Linking \"%s\" ]", get_realpath());
+    LD_DEBUG(any, "si->base = %p si->flags = 0x%08x", reinterpret_cast<void*>(base), flags_);
   }
 
   if (dynamic == nullptr) {
@@ -2853,7 +2851,7 @@
     return false;
   } else {
     if (!relocating_linker) {
-      DEBUG("dynamic = %p", dynamic);
+      LD_DEBUG(dynamic, "dynamic section @%p", dynamic);
     }
   }
 
@@ -2883,8 +2881,8 @@
   // source: http://www.sco.com/developers/gabi/1998-04-29/ch5.dynamic.html
   uint32_t needed_count = 0;
   for (ElfW(Dyn)* d = dynamic; d->d_tag != DT_NULL; ++d) {
-    DEBUG("d = %p, d[0](tag) = %p d[1](val) = %p",
-          d, reinterpret_cast<void*>(d->d_tag), reinterpret_cast<void*>(d->d_un.d_val));
+    LD_DEBUG(dynamic, "dynamic entry @%p: d_tag=%p, d_val=%p",
+             d, reinterpret_cast<void*>(d->d_tag), reinterpret_cast<void*>(d->d_un.d_val));
     switch (d->d_tag) {
       case DT_SONAME:
         // this is parsed after we have strtab initialized (see below).
@@ -3098,17 +3096,17 @@
 
       case DT_INIT:
         init_func_ = reinterpret_cast<linker_ctor_function_t>(load_bias + d->d_un.d_ptr);
-        DEBUG("%s constructors (DT_INIT) found at %p", get_realpath(), init_func_);
+        LD_DEBUG(dynamic, "%s constructors (DT_INIT) found at %p", get_realpath(), init_func_);
         break;
 
       case DT_FINI:
         fini_func_ = reinterpret_cast<linker_dtor_function_t>(load_bias + d->d_un.d_ptr);
-        DEBUG("%s destructors (DT_FINI) found at %p", get_realpath(), fini_func_);
+        LD_DEBUG(dynamic, "%s destructors (DT_FINI) found at %p", get_realpath(), fini_func_);
         break;
 
       case DT_INIT_ARRAY:
         init_array_ = reinterpret_cast<linker_ctor_function_t*>(load_bias + d->d_un.d_ptr);
-        DEBUG("%s constructors (DT_INIT_ARRAY) found at %p", get_realpath(), init_array_);
+        LD_DEBUG(dynamic, "%s constructors (DT_INIT_ARRAY) found at %p", get_realpath(), init_array_);
         break;
 
       case DT_INIT_ARRAYSZ:
@@ -3117,7 +3115,7 @@
 
       case DT_FINI_ARRAY:
         fini_array_ = reinterpret_cast<linker_dtor_function_t*>(load_bias + d->d_un.d_ptr);
-        DEBUG("%s destructors (DT_FINI_ARRAY) found at %p", get_realpath(), fini_array_);
+        LD_DEBUG(dynamic, "%s destructors (DT_FINI_ARRAY) found at %p", get_realpath(), fini_array_);
         break;
 
       case DT_FINI_ARRAYSZ:
@@ -3126,7 +3124,7 @@
 
       case DT_PREINIT_ARRAY:
         preinit_array_ = reinterpret_cast<linker_ctor_function_t*>(load_bias + d->d_un.d_ptr);
-        DEBUG("%s constructors (DT_PREINIT_ARRAY) found at %p", get_realpath(), preinit_array_);
+        LD_DEBUG(dynamic, "%s constructors (DT_PREINIT_ARRAY) found at %p", get_realpath(), preinit_array_);
         break;
 
       case DT_PREINIT_ARRAYSZ:
@@ -3266,8 +3264,8 @@
     }
   }
 
-  DEBUG("si->base = %p, si->strtab = %p, si->symtab = %p",
-        reinterpret_cast<void*>(base), strtab_, symtab_);
+  LD_DEBUG(dynamic, "si->base = %p, si->strtab = %p, si->symtab = %p",
+           reinterpret_cast<void*>(base), strtab_, symtab_);
 
   // Validity checks.
   if (relocating_linker && needed_count != 0) {
@@ -3374,7 +3372,7 @@
     return false;
   }
 
-  DEBUG("[ finished linking %s ]", get_realpath());
+  LD_DEBUG(any, "[ finished linking %s ]", get_realpath());
 
 #if !defined(__LP64__)
   if (has_text_relocations) {
@@ -3556,7 +3554,7 @@
 
   {
     std::string ld_config_file_path = get_ld_config_file_path(executable_path);
-    INFO("[ Reading linker config \"%s\" ]", ld_config_file_path.c_str());
+    LD_DEBUG(any, "[ Reading linker config \"%s\" ]", ld_config_file_path.c_str());
     ScopedTrace trace(("linker config " + ld_config_file_path).c_str());
     std::string error_msg;
     if (!Config::read_binary_config(ld_config_file_path.c_str(), executable_path, g_is_asan, g_is_hwasan,
diff --git a/linker/linker_cfi.cpp b/linker/linker_cfi.cpp
index 247a25d..92ec53e 100644
--- a/linker/linker_cfi.cpp
+++ b/linker/linker_cfi.cpp
@@ -166,13 +166,13 @@
   }
   uintptr_t cfi_check = soinfo_find_cfi_check(si);
   if (cfi_check == 0) {
-    INFO("[ CFI add 0x%zx + 0x%zx %s ]", static_cast<uintptr_t>(si->base),
+    LD_DEBUG(cfi, "[ CFI add 0x%zx + 0x%zx %s ]", static_cast<uintptr_t>(si->base),
          static_cast<uintptr_t>(si->size), si->get_soname());
     AddUnchecked(si->base, si->base + si->size);
     return true;
   }
 
-  INFO("[ CFI add 0x%zx + 0x%zx %s: 0x%zx ]", static_cast<uintptr_t>(si->base),
+  LD_DEBUG(cfi, "[ CFI add 0x%zx + 0x%zx %s: 0x%zx ]", static_cast<uintptr_t>(si->base),
        static_cast<uintptr_t>(si->size), si->get_soname(), cfi_check);
 #ifdef __arm__
   // Require Thumb encoding.
@@ -263,8 +263,8 @@
 void CFIShadowWriter::BeforeUnload(soinfo* si) {
   if (shadow_start == nullptr) return;
   if (si->base == 0 || si->size == 0) return;
-  INFO("[ CFI remove 0x%zx + 0x%zx: %s ]", static_cast<uintptr_t>(si->base),
-       static_cast<uintptr_t>(si->size), si->get_soname());
+  LD_DEBUG(cfi, "[ CFI remove 0x%zx + 0x%zx: %s ]", static_cast<uintptr_t>(si->base),
+           static_cast<uintptr_t>(si->size), si->get_soname());
   AddInvalid(si->base, si->base + si->size);
   FixupVmaName();
 }
diff --git a/linker/linker_config.cpp b/linker/linker_config.cpp
index 73ae2ef..35a93fc 100644
--- a/linker/linker_config.cpp
+++ b/linker/linker_config.cpp
@@ -251,10 +251,8 @@
         // the failure with INFO rather than DL_WARN. e.g. A binary in
         // /data/local/tmp may attempt to stat /postinstall. See
         // http://b/120996057.
-        INFO("%s:%zd: warning: path \"%s\" couldn't be resolved: %m",
-             ld_config_file_path,
-             cp.lineno(),
-             value.c_str());
+        LD_DEBUG(any, "%s:%zd: warning: path \"%s\" couldn't be resolved: %m",
+                 ld_config_file_path, cp.lineno(), value.c_str());
         resolved_path = value;
       }
 
@@ -265,7 +263,7 @@
     }
   }
 
-  INFO("[ Using config section \"%s\" ]", section_name.c_str());
+  LD_DEBUG(any, "[ Using config section \"%s\" ]", section_name.c_str());
 
   // skip everything until we meet a correct section
   while (true) {
diff --git a/linker/linker_debug.cpp b/linker/linker_debug.cpp
index e6211f7..430a151 100644
--- a/linker/linker_debug.cpp
+++ b/linker/linker_debug.cpp
@@ -30,19 +30,76 @@
 
 #include <unistd.h>
 
-void linker_log_va_list(int prio, const char* fmt, va_list ap) {
+#include <android-base/strings.h>
+
+LinkerDebugConfig g_linker_debug_config;
+
+void init_LD_DEBUG(const std::string& value) {
+  if (value.empty()) return;
+  std::vector<std::string> options = android::base::Split(value, ",");
+  for (const auto& o : options) {
+    if (o == "calls") g_linker_debug_config.calls = true;
+    else if (o == "cfi") g_linker_debug_config.cfi = true;
+    else if (o == "dynamic") g_linker_debug_config.dynamic = true;
+    else if (o == "lookup") g_linker_debug_config.lookup = true;
+    else if (o == "props") g_linker_debug_config.props = true;
+    else if (o == "reloc") g_linker_debug_config.reloc = true;
+    else if (o == "statistics") g_linker_debug_config.statistics = true;
+    else if (o == "timing") g_linker_debug_config.timing = true;
+    else if (o == "all") {
+      g_linker_debug_config.calls = true;
+      g_linker_debug_config.cfi = true;
+      g_linker_debug_config.dynamic = true;
+      g_linker_debug_config.lookup = true;
+      g_linker_debug_config.props = true;
+      g_linker_debug_config.reloc = true;
+      g_linker_debug_config.statistics = true;
+      g_linker_debug_config.timing = true;
+    } else {
+      __linker_error("$LD_DEBUG is a comma-separated list of:\n"
+                     "\n"
+                     "  calls       ctors/dtors/ifuncs\n"
+                     "  cfi         control flow integrity messages\n"
+                     "  dynamic     dynamic section processing\n"
+                     "  lookup      symbol lookup\n"
+                     "  props       ELF property processing\n"
+                     "  reloc       relocation resolution\n"
+                     "  statistics  relocation statistics\n"
+                     "  timing      timing information\n"
+                     "\n"
+                     "or 'all' for all of the above.\n");
+    }
+  }
+  if (g_linker_debug_config.calls || g_linker_debug_config.cfi ||
+      g_linker_debug_config.dynamic || g_linker_debug_config.lookup ||
+      g_linker_debug_config.props || g_linker_debug_config.reloc ||
+      g_linker_debug_config.statistics || g_linker_debug_config.timing) {
+    g_linker_debug_config.any = true;
+  }
+}
+
+static void linker_log_va_list(int prio, const char* fmt, va_list ap) {
   va_list ap2;
   va_copy(ap2, ap);
-  async_safe_format_log_va_list(5 - prio, "linker", fmt, ap2);
+  async_safe_format_log_va_list(prio, "linker", fmt, ap2);
   va_end(ap2);
 
   async_safe_format_fd_va_list(STDERR_FILENO, fmt, ap);
   write(STDERR_FILENO, "\n", 1);
 }
 
-void linker_log(int prio, const char* fmt, ...) {
+void __linker_log(int prio, const char* fmt, ...) {
   va_list ap;
   va_start(ap, fmt);
   linker_log_va_list(prio, fmt, ap);
   va_end(ap);
 }
+
+void __linker_error(const char* fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  linker_log_va_list(ANDROID_LOG_FATAL, fmt, ap);
+  va_end(ap);
+
+  _exit(EXIT_FAILURE);
+}
diff --git a/linker/linker_debug.h b/linker/linker_debug.h
index 3aab185..e5f17c4 100644
--- a/linker/linker_debug.h
+++ b/linker/linker_debug.h
@@ -28,54 +28,45 @@
 
 #pragma once
 
-// You can increase the verbosity of debug traces by defining the LD_DEBUG
-// environment variable to a numeric value from 0 to 2 (corresponding to
-// INFO, TRACE, and DEBUG calls in the source). This will only
-// affect new processes being launched.
-
-#define TRACE_DEBUG          1
-#define DO_TRACE_LOOKUP      1
-#define DO_TRACE_RELO        1
-#define DO_TRACE_IFUNC       1
-#define TIMING               0
-#define STATS                0
-
-/*********************************************************************
- * You shouldn't need to modify anything below unless you are adding
- * more debugging information.
- *
- * To enable/disable specific debug options, change the defines above
- *********************************************************************/
-
 #include <stdarg.h>
 #include <unistd.h>
 
+#include <string>
+
 #include <async_safe/log.h>
 #include <async_safe/CHECK.h>
 
-#define LINKER_VERBOSITY_PRINT (-1)
-#define LINKER_VERBOSITY_INFO   0
-#define LINKER_VERBOSITY_TRACE  1
-#define LINKER_VERBOSITY_DEBUG  2
+struct LinkerDebugConfig {
+  // Set automatically if any of the more specific options are set.
+  bool any;
 
-__LIBC_HIDDEN__ extern int g_ld_debug_verbosity;
+  // Messages relating to calling ctors/dtors/ifuncs.
+  bool calls;
+  // Messages relating to CFI.
+  bool cfi;
+  // Messages relating to the dynamic section.
+  bool dynamic;
+  // Messages relating to symbol lookup.
+  bool lookup;
+  // Messages relating to relocation processing.
+  bool reloc;
+  // Messages relating to ELF properties.
+  bool props;
+  // TODO: "config" and "zip" seem likely to want to be separate?
 
-__LIBC_HIDDEN__ void linker_log_va_list(int prio, const char* fmt, va_list ap);
-__LIBC_HIDDEN__ void linker_log(int prio, const char* fmt, ...) __printflike(2, 3);
+  bool timing;
+  bool statistics;
+};
 
-#define _PRINTVF(v, x...) \
-    do { \
-      if (g_ld_debug_verbosity > (v)) linker_log((v), x); \
-    } while (0)
+extern LinkerDebugConfig g_linker_debug_config;
 
-#define PRINT(x...)          _PRINTVF(LINKER_VERBOSITY_PRINT, x)
-#define INFO(x...)           _PRINTVF(LINKER_VERBOSITY_INFO, x)
-#define TRACE(x...)          _PRINTVF(LINKER_VERBOSITY_TRACE, x)
+__LIBC_HIDDEN__ void init_LD_DEBUG(const std::string& value);
+__LIBC_HIDDEN__ void __linker_log(int prio, const char* fmt, ...) __printflike(2, 3);
+__LIBC_HIDDEN__ void __linker_error(const char* fmt, ...) __printflike(1, 2);
 
-#if TRACE_DEBUG
-#define DEBUG(x...)          _PRINTVF(LINKER_VERBOSITY_DEBUG, "DEBUG: " x)
-#else /* !TRACE_DEBUG */
-#define DEBUG(x...)          do {} while (0)
-#endif /* TRACE_DEBUG */
-
-#define TRACE_TYPE(t, x...)   do { if (DO_TRACE_##t) { TRACE(x); } } while (0)
+#define LD_DEBUG(what, x...) \
+  do { \
+    if (g_linker_debug_config.what) { \
+      __linker_log(ANDROID_LOG_INFO, x); \
+    } \
+  } while (false)
diff --git a/linker/linker_globals.h b/linker/linker_globals.h
index 0cb7ca9..2bfdccd 100644
--- a/linker/linker_globals.h
+++ b/linker/linker_globals.h
@@ -54,7 +54,7 @@
 #define DL_ERR_AND_LOG(fmt, x...) \
   do { \
     DL_ERR(fmt, ##x); \
-    PRINT(fmt, ##x); \
+    __linker_log(ANDROID_LOG_ERROR, fmt, ##x); \
   } while (false)
 
 #define DL_OPEN_ERR(fmt, x...) \
diff --git a/linker/linker_main.cpp b/linker/linker_main.cpp
index 86b6509..6ccd75b 100644
--- a/linker/linker_main.cpp
+++ b/linker/linker_main.cpp
@@ -73,21 +73,6 @@
 void __libc_init_mte(const memtag_dynamic_entries_t* memtag_dynamic_entries, const void* phdr_start,
                      size_t phdr_count, uintptr_t load_bias, void* stack_top);
 
-__printflike(1, 2) static void __linker_error(const char* fmt, ...) {
-  va_list ap;
-
-  va_start(ap, fmt);
-  async_safe_format_fd_va_list(STDERR_FILENO, fmt, ap);
-  write(STDERR_FILENO, "\n", 1);
-  va_end(ap);
-
-  va_start(ap, fmt);
-  async_safe_format_log_va_list(ANDROID_LOG_FATAL, "linker", fmt, ap);
-  va_end(ap);
-
-  _exit(EXIT_FAILURE);
-}
-
 static void __linker_cannot_link(const char* argv0) {
   __linker_error("CANNOT LINK EXECUTABLE \"%s\": %s", argv0, linker_get_error_buffer());
 }
@@ -119,7 +104,7 @@
 
   if (trav == nullptr) {
     // si was not in solist
-    PRINT("name \"%s\"@%p is not in solist!", si->get_realpath(), si);
+    DL_WARN("name \"%s\"@%p is not in solist!", si->get_realpath(), si);
     return false;
   }
 
@@ -147,7 +132,6 @@
 }
 
 bool g_is_ldd;
-int g_ld_debug_verbosity;
 
 static std::vector<std::string> g_ld_preload_names;
 
@@ -296,10 +280,8 @@
 static ElfW(Addr) linker_main(KernelArgumentBlock& args, const char* exe_to_load) {
   ProtectedDataGuard guard;
 
-#if TIMING
-  struct timeval t0, t1;
-  gettimeofday(&t0, 0);
-#endif
+  timeval t0, t1;
+  gettimeofday(&t0, nullptr);
 
   // Sanitize the environment.
   __libc_init_AT_SECURE(args.envp);
@@ -317,13 +299,11 @@
 
   // Enable debugging logs?
   const char* LD_DEBUG = getenv("LD_DEBUG");
-  if (LD_DEBUG != nullptr) {
-    g_ld_debug_verbosity = atoi(LD_DEBUG);
-  }
+  if (LD_DEBUG != nullptr) init_LD_DEBUG(LD_DEBUG);
 
   if (getenv("LD_SHOW_AUXV") != nullptr) ld_show_auxv(args.auxv);
 
-  INFO("[ Android dynamic linker (" ABI_STRING ") ]");
+  LD_DEBUG(any, "[ Android dynamic linker (" ABI_STRING ") ]");
 
   // These should have been sanitized by __libc_init_AT_SECURE, but the test
   // doesn't cost us anything.
@@ -332,18 +312,18 @@
   if (!getauxval(AT_SECURE)) {
     ldpath_env = getenv("LD_LIBRARY_PATH");
     if (ldpath_env != nullptr) {
-      INFO("[ LD_LIBRARY_PATH set to \"%s\" ]", ldpath_env);
+      LD_DEBUG(any, "[ LD_LIBRARY_PATH set to \"%s\" ]", ldpath_env);
     }
     ldpreload_env = getenv("LD_PRELOAD");
     if (ldpreload_env != nullptr) {
-      INFO("[ LD_PRELOAD set to \"%s\" ]", ldpreload_env);
+      LD_DEBUG(any, "[ LD_PRELOAD set to \"%s\" ]", ldpreload_env);
     }
   }
 
   const ExecutableInfo exe_info = exe_to_load ? load_executable(exe_to_load) :
                                                 get_executable_info(args.argv[0]);
 
-  INFO("[ Linking executable \"%s\" ]", exe_info.path.c_str());
+  LD_DEBUG(any, "[ Linking executable \"%s\" ]", exe_info.path.c_str());
 
   // Initialize the main exe's soinfo.
   soinfo* si = soinfo_alloc(&g_default_namespace,
@@ -496,27 +476,22 @@
   si->call_pre_init_constructors();
   si->call_constructors();
 
-#if TIMING
-  gettimeofday(&t1, nullptr);
-  PRINT("LINKER TIME: %s: %d microseconds", g_argv[0],
-        static_cast<int>(((static_cast<long long>(t1.tv_sec) * 1000000LL) +
-                          static_cast<long long>(t1.tv_usec)) -
-                         ((static_cast<long long>(t0.tv_sec) * 1000000LL) +
-                          static_cast<long long>(t0.tv_usec))));
-#endif
-#if STATS
-  print_linker_stats();
-#endif
-#if TIMING || STATS
-  fflush(stdout);
-#endif
+  if (g_linker_debug_config.timing) {
+    gettimeofday(&t1, nullptr);
+    long long t0_us = (t0.tv_sec * 1000000LL) + t0.tv_usec;
+    long long t1_us = (t1.tv_sec * 1000000LL) + t1.tv_usec;
+    LD_DEBUG(timing, "LINKER TIME: %s: %lld microseconds", g_argv[0], t1_us - t0_us);
+  }
+  if (g_linker_debug_config.statistics) {
+    print_linker_stats();
+  }
 
   // We are about to hand control over to the executable loaded.  We don't want
   // to leave dirty pages behind unnecessarily.
   purge_unused_memory();
 
   ElfW(Addr) entry = exe_info.entry_point;
-  TRACE("[ Ready to execute \"%s\" @ %p ]", si->get_realpath(), reinterpret_cast<void*>(entry));
+  LD_DEBUG(any, "[ Ready to execute \"%s\" @ %p ]", si->get_realpath(), reinterpret_cast<void*>(entry));
   return entry;
 }
 
@@ -833,7 +808,7 @@
 
   ElfW(Addr) start_address = linker_main(args, exe_to_load);
 
-  INFO("[ Jumping to _start (%p)... ]", reinterpret_cast<void*>(start_address));
+  LD_DEBUG(any, "[ Jumping to _start (%p)... ]", reinterpret_cast<void*>(start_address));
 
   // Return the address that the calling assembly stub should jump to.
   return start_address;
diff --git a/linker/linker_note_gnu_property.cpp b/linker/linker_note_gnu_property.cpp
index be1aebc..082a604 100644
--- a/linker/linker_note_gnu_property.cpp
+++ b/linker/linker_note_gnu_property.cpp
@@ -62,7 +62,7 @@
         continue;
       }
 
-      TRACE("\"%s\" PT_GNU_PROPERTY: found at segment index %zu", name, i);
+      LD_DEBUG(props, "\"%s\" PT_GNU_PROPERTY: found at segment index %zu", name, i);
 
       // Check segment size.
       if (phdr[i].p_memsz < sizeof(ElfW(NhdrGNUProperty))) {
@@ -90,7 +90,7 @@
     }
   }
 
-  TRACE("\"%s\" PT_GNU_PROPERTY: not found", name);
+  LD_DEBUG(props, "\"%s\" PT_GNU_PROPERTY: not found", name);
   return nullptr;
 }
 
@@ -122,7 +122,7 @@
   // The total length of the program property array is in _bytes_.
   ElfW(Word) offset = 0;
   while (offset < note_nhdr->nhdr.n_descsz) {
-    DEBUG("\"%s\" .note.gnu.property: processing at offset 0x%x", name, offset);
+    LD_DEBUG(props, "\"%s\" .note.gnu.property: processing at offset 0x%x", name, offset);
 
     // At least the "header" part must fit.
     // The ABI doesn't say that pr_datasz can't be 0.
@@ -161,14 +161,14 @@
         const ElfW(Word) flags = *reinterpret_cast<const ElfW(Word)*>(&property->pr_data[0]);
         properties_.bti_compatible = (flags & GNU_PROPERTY_AARCH64_FEATURE_1_BTI) != 0;
         if (properties_.bti_compatible) {
-          INFO("[ BTI compatible: \"%s\" ]", name);
+          LD_DEBUG(props, "[ BTI compatible: \"%s\" ]", name);
         }
         break;
       }
 #endif
       default:
-        DEBUG("\"%s\" .note.gnu.property: found property pr_type %u pr_datasz 0x%x", name,
-              property->pr_type, property->pr_datasz);
+        LD_DEBUG(props, "\"%s\" .note.gnu.property: found property pr_type %u pr_datasz 0x%x",
+                 name, property->pr_type, property->pr_datasz);
         break;
     }
 
diff --git a/linker/linker_phdr.cpp b/linker/linker_phdr.cpp
index e89acb5..48206be 100644
--- a/linker/linker_phdr.cpp
+++ b/linker/linker_phdr.cpp
@@ -851,6 +851,106 @@
   *p_filesz += extend;
 }
 
+bool ElfReader::MapSegment(size_t seg_idx, size_t len) {
+  const ElfW(Phdr)* phdr = &phdr_table_[seg_idx];
+
+  void* start = reinterpret_cast<void*>(page_start(phdr->p_vaddr + load_bias_));
+
+  // The ELF could be being loaded directly from a zipped APK,
+  // the zip offset must be added to find the segment offset.
+  const ElfW(Addr) offset = file_offset_ + page_start(phdr->p_offset);
+
+  int prot = PFLAGS_TO_PROT(phdr->p_flags);
+
+  void* seg_addr = mmap64(start, len, prot, MAP_FIXED | MAP_PRIVATE, fd_, offset);
+
+  if (seg_addr == MAP_FAILED) {
+    DL_ERR("couldn't map \"%s\" segment %zd: %m", name_.c_str(), seg_idx);
+    return false;
+  }
+
+  // Mark segments as huge page eligible if they meet the requirements
+  if ((phdr->p_flags & PF_X) && phdr->p_align == kPmdSize &&
+      get_transparent_hugepages_supported()) {
+    madvise(seg_addr, len, MADV_HUGEPAGE);
+  }
+
+  return true;
+}
+
+void ElfReader::ZeroFillSegment(const ElfW(Phdr)* phdr) {
+  ElfW(Addr) seg_start = phdr->p_vaddr + load_bias_;
+  uint64_t unextended_seg_file_end = seg_start + phdr->p_filesz;
+
+  // If the segment is writable, and does not end on a page boundary,
+  // zero-fill it until the page limit.
+  //
+  // Do not attempt to zero the extended region past the first partial page,
+  // since doing so may:
+  //   1) Result in a SIGBUS, as the region is not backed by the underlying
+  //      file.
+  //   2) Break the COW backing, faulting in new anon pages for a region
+  //      that will not be used.
+  if ((phdr->p_flags & PF_W) != 0 && page_offset(unextended_seg_file_end) > 0) {
+    memset(reinterpret_cast<void*>(unextended_seg_file_end), 0,
+           kPageSize - page_offset(unextended_seg_file_end));
+  }
+}
+
+void ElfReader::DropPaddingPages(const ElfW(Phdr)* phdr, uint64_t seg_file_end) {
+  ElfW(Addr) seg_start = phdr->p_vaddr + load_bias_;
+  uint64_t unextended_seg_file_end = seg_start + phdr->p_filesz;
+
+  uint64_t pad_start = page_end(unextended_seg_file_end);
+  uint64_t pad_end = page_end(seg_file_end);
+  CHECK(pad_start <= pad_end);
+
+  uint64_t pad_len = pad_end - pad_start;
+  if (pad_len == 0 || !page_size_migration_supported()) {
+    return;
+  }
+
+  // Pages may be brought in due to readahead.
+  // Drop the padding (zero) pages, to avoid reclaim work later.
+  //
+  // NOTE: The madvise() here is special, as it also serves to hint to the
+  // kernel the portion of the LOAD segment that is padding.
+  //
+  // See: [1] https://android-review.googlesource.com/c/kernel/common/+/3032411
+  //      [2] https://android-review.googlesource.com/c/kernel/common/+/3048835
+  if (madvise(reinterpret_cast<void*>(pad_start), pad_len, MADV_DONTNEED)) {
+    DL_WARN("\"%s\": madvise(0x%" PRIx64 ", 0x%" PRIx64 ", MADV_DONTNEED) failed: %m",
+            name_.c_str(), pad_start, pad_len);
+  }
+}
+
+bool ElfReader::MapBssSection(const ElfW(Phdr)* phdr, ElfW(Addr) seg_page_end,
+                              ElfW(Addr) seg_file_end) {
+  // seg_file_end is now the first page address after the file content.
+  seg_file_end = page_end(seg_file_end);
+
+  if (seg_page_end <= seg_file_end) {
+    return true;
+  }
+
+  // If seg_page_end is larger than seg_file_end, we need to zero
+  // anything between them. This is done by using a private anonymous
+  // map for all extra pages
+  size_t zeromap_size = seg_page_end - seg_file_end;
+  void* zeromap =
+      mmap(reinterpret_cast<void*>(seg_file_end), zeromap_size, PFLAGS_TO_PROT(phdr->p_flags),
+           MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+  if (zeromap == MAP_FAILED) {
+    DL_ERR("couldn't map .bss section for \"%s\": %m", name_.c_str());
+    return false;
+  }
+
+  // Set the VMA name using prctl
+  prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, zeromap, zeromap_size, ".bss");
+
+  return true;
+}
+
 bool ElfReader::LoadSegments() {
   size_t min_palign = phdr_table_get_minimum_alignment(phdr_table_, phdr_num_);
   // Only enforce this on 16 KB systems. Apps may rely on undefined behavior
@@ -917,79 +1017,18 @@
         add_dlwarning(name_.c_str(), "W+E load segments");
       }
 
-      void* seg_addr = mmap64(reinterpret_cast<void*>(seg_page_start),
-                            file_length,
-                            prot,
-                            MAP_FIXED|MAP_PRIVATE,
-                            fd_,
-                            file_offset_ + file_page_start);
-      if (seg_addr == MAP_FAILED) {
-        DL_ERR("couldn't map \"%s\" segment %zd: %m", name_.c_str(), i);
+      // Pass the file_length, since it may have been extended by _extend_load_segment_vma().
+      if (!MapSegment(i, file_length)) {
         return false;
       }
-
-      // Mark segments as huge page eligible if they meet the requirements
-      // (executable and PMD aligned).
-      if ((phdr->p_flags & PF_X) && phdr->p_align == kPmdSize &&
-          get_transparent_hugepages_supported()) {
-        madvise(seg_addr, file_length, MADV_HUGEPAGE);
-      }
     }
 
-    // if the segment is writable, and does not end on a page boundary,
-    // zero-fill it until the page limit.
-    //
-    // Do not attempt to zero the extended region past the first partial page,
-    // since doing so may:
-    //   1) Result in a SIGBUS, as the region is not backed by the underlying
-    //      file.
-    //   2) Break the COW backing, faulting in new anon pages for a region
-    //      that will not be used.
+    ZeroFillSegment(phdr);
 
-    uint64_t unextended_seg_file_end = seg_start + phdr->p_filesz;
-    if ((phdr->p_flags & PF_W) != 0 && page_offset(unextended_seg_file_end) > 0) {
-      memset(reinterpret_cast<void*>(unextended_seg_file_end), 0,
-             kPageSize - page_offset(unextended_seg_file_end));
-    }
+    DropPaddingPages(phdr, seg_file_end);
 
-    // Pages may be brought in due to readahead.
-    // Drop the padding (zero) pages, to avoid reclaim work later.
-    //
-    // NOTE: The madvise() here is special, as it also serves to hint to the
-    // kernel the portion of the LOAD segment that is padding.
-    //
-    // See: [1] https://android-review.googlesource.com/c/kernel/common/+/3032411
-    //      [2] https://android-review.googlesource.com/c/kernel/common/+/3048835
-    uint64_t pad_start = page_end(unextended_seg_file_end);
-    uint64_t pad_end = page_end(seg_file_end);
-    CHECK(pad_start <= pad_end);
-    uint64_t pad_len = pad_end - pad_start;
-    if (page_size_migration_supported() && pad_len > 0 &&
-        madvise(reinterpret_cast<void*>(pad_start), pad_len, MADV_DONTNEED)) {
-      DL_WARN("\"%s\": madvise(0x%" PRIx64 ", 0x%" PRIx64 ", MADV_DONTNEED) failed: %m",
-              name_.c_str(), pad_start, pad_len);
-    }
-
-    seg_file_end = page_end(seg_file_end);
-
-    // seg_file_end is now the first page address after the file
-    // content. If seg_end is larger, we need to zero anything
-    // between them. This is done by using a private anonymous
-    // map for all extra pages.
-    if (seg_page_end > seg_file_end) {
-      size_t zeromap_size = seg_page_end - seg_file_end;
-      void* zeromap = mmap(reinterpret_cast<void*>(seg_file_end),
-                           zeromap_size,
-                           PFLAGS_TO_PROT(phdr->p_flags),
-                           MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE,
-                           -1,
-                           0);
-      if (zeromap == MAP_FAILED) {
-        DL_ERR("couldn't zero fill \"%s\" gap: %m", name_.c_str());
-        return false;
-      }
-
-      prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, zeromap, zeromap_size, ".bss");
+    if (!MapBssSection(phdr, seg_page_end, seg_file_end)) {
+      return false;
     }
   }
   return true;
diff --git a/linker/linker_phdr.h b/linker/linker_phdr.h
index e865a03..1d6bbe3 100644
--- a/linker/linker_phdr.h
+++ b/linker/linker_phdr.h
@@ -68,6 +68,11 @@
   [[nodiscard]] bool ReadDynamicSection();
   [[nodiscard]] bool ReadPadSegmentNote();
   [[nodiscard]] bool ReserveAddressSpace(address_space_params* address_space);
+  [[nodiscard]] bool MapSegment(size_t seg_idx, size_t len);
+  void ZeroFillSegment(const ElfW(Phdr)* phdr);
+  void DropPaddingPages(const ElfW(Phdr)* phdr, uint64_t seg_file_end);
+  [[nodiscard]] bool MapBssSection(const ElfW(Phdr)* phdr, ElfW(Addr) seg_page_end,
+                                   ElfW(Addr) seg_file_end);
   [[nodiscard]] bool LoadSegments();
   [[nodiscard]] bool FindPhdr();
   [[nodiscard]] bool FindGnuPropertySection();
diff --git a/linker/linker_relocate.cpp b/linker/linker_relocate.cpp
index 8f85871..bcb1efc 100644
--- a/linker/linker_relocate.cpp
+++ b/linker/linker_relocate.cpp
@@ -147,12 +147,13 @@
 }
 
 void print_linker_stats() {
-  PRINT("RELO STATS: %s: %d abs, %d rel, %d symbol (%d cached)",
-         g_argv[0],
-         linker_stats.count[kRelocAbsolute],
-         linker_stats.count[kRelocRelative],
-         linker_stats.count[kRelocSymbol],
-         linker_stats.count[kRelocSymbolCached]);
+  LD_DEBUG(statistics,
+           "RELO STATS: %s: %d abs, %d rel, %d symbol (%d cached)",
+           g_argv[0],
+           linker_stats.count[kRelocAbsolute],
+           linker_stats.count[kRelocRelative],
+           linker_stats.count[kRelocSymbol],
+           linker_stats.count[kRelocSymbolCached]);
 }
 
 static bool process_relocation_general(Relocator& relocator, const rel_t& reloc);
@@ -207,20 +208,9 @@
   };
 #endif
 
-  auto trace_reloc = [](const char* fmt, ...) __printflike(2, 3) {
-    if (IsGeneral &&
-        g_ld_debug_verbosity > LINKER_VERBOSITY_TRACE &&
-        DO_TRACE_RELO) {
-      va_list ap;
-      va_start(ap, fmt);
-      linker_log_va_list(LINKER_VERBOSITY_TRACE, fmt, ap);
-      va_end(ap);
-    }
-  };
-
   // Skip symbol lookup for R_GENERIC_NONE relocations.
   if (__predict_false(r_type == R_GENERIC_NONE)) {
-    trace_reloc("RELO NONE");
+    LD_DEBUG(reloc && IsGeneral, "RELO NONE");
     return true;
   }
 
@@ -313,8 +303,8 @@
     if (r_type == R_GENERIC_JUMP_SLOT) {
       count_relocation_if<IsGeneral>(kRelocAbsolute);
       const ElfW(Addr) result = sym_addr + get_addend_norel();
-      trace_reloc("RELO JMP_SLOT %16p <- %16p %s",
-                  rel_target, reinterpret_cast<void*>(result), sym_name);
+      LD_DEBUG(reloc && IsGeneral, "RELO JMP_SLOT %16p <- %16p %s",
+               rel_target, reinterpret_cast<void*>(result), sym_name);
       *static_cast<ElfW(Addr)*>(rel_target) = result;
       return true;
     }
@@ -327,8 +317,8 @@
     if (r_type == R_GENERIC_ABSOLUTE) {
       count_relocation_if<IsGeneral>(kRelocAbsolute);
       const ElfW(Addr) result = sym_addr + get_addend_rel();
-      trace_reloc("RELO ABSOLUTE %16p <- %16p %s",
-                  rel_target, reinterpret_cast<void*>(result), sym_name);
+      LD_DEBUG(reloc && IsGeneral, "RELO ABSOLUTE %16p <- %16p %s",
+               rel_target, reinterpret_cast<void*>(result), sym_name);
       *static_cast<ElfW(Addr)*>(rel_target) = result;
       return true;
     } else if (r_type == R_GENERIC_GLOB_DAT) {
@@ -337,8 +327,8 @@
       // it.
       count_relocation_if<IsGeneral>(kRelocAbsolute);
       const ElfW(Addr) result = sym_addr + get_addend_norel();
-      trace_reloc("RELO GLOB_DAT %16p <- %16p %s",
-                  rel_target, reinterpret_cast<void*>(result), sym_name);
+      LD_DEBUG(reloc && IsGeneral, "RELO GLOB_DAT %16p <- %16p %s",
+               rel_target, reinterpret_cast<void*>(result), sym_name);
       *static_cast<ElfW(Addr)*>(rel_target) = result;
       return true;
     } else if (r_type == R_GENERIC_RELATIVE) {
@@ -346,8 +336,8 @@
       // referenced symbol (and abort if the symbol isn't found), even though it isn't used.
       count_relocation_if<IsGeneral>(kRelocRelative);
       const ElfW(Addr) result = relocator.si->load_bias + get_addend_rel();
-      trace_reloc("RELO RELATIVE %16p <- %16p",
-                  rel_target, reinterpret_cast<void*>(result));
+      LD_DEBUG(reloc && IsGeneral, "RELO RELATIVE %16p <- %16p",
+               rel_target, reinterpret_cast<void*>(result));
       *static_cast<ElfW(Addr)*>(rel_target) = result;
       return true;
     }
@@ -368,8 +358,8 @@
       if (!relocator.si->is_linker()) {
         count_relocation_if<IsGeneral>(kRelocRelative);
         const ElfW(Addr) ifunc_addr = relocator.si->load_bias + get_addend_rel();
-        trace_reloc("RELO IRELATIVE %16p <- %16p",
-                    rel_target, reinterpret_cast<void*>(ifunc_addr));
+        LD_DEBUG(reloc && IsGeneral, "RELO IRELATIVE %16p <- %16p",
+                 rel_target, reinterpret_cast<void*>(ifunc_addr));
         if (handle_text_relocs && !protect_segments()) return false;
         const ElfW(Addr) result = call_ifunc_resolver(ifunc_addr);
         if (handle_text_relocs && !unprotect_segments()) return false;
@@ -406,8 +396,8 @@
           }
         }
         tpoff += sym_addr + get_addend_rel();
-        trace_reloc("RELO TLS_TPREL %16p <- %16p %s",
-                    rel_target, reinterpret_cast<void*>(tpoff), sym_name);
+        LD_DEBUG(reloc && IsGeneral, "RELO TLS_TPREL %16p <- %16p %s",
+                 rel_target, reinterpret_cast<void*>(tpoff), sym_name);
         *static_cast<ElfW(Addr)*>(rel_target) = tpoff;
       }
       break;
@@ -422,8 +412,8 @@
           module_id = found_in->get_tls()->module_id;
           CHECK(module_id != kTlsUninitializedModuleId);
         }
-        trace_reloc("RELO TLS_DTPMOD %16p <- %zu %s",
-                    rel_target, module_id, sym_name);
+        LD_DEBUG(reloc && IsGeneral, "RELO TLS_DTPMOD %16p <- %zu %s",
+                 rel_target, module_id, sym_name);
         *static_cast<ElfW(Addr)*>(rel_target) = module_id;
       }
       break;
@@ -431,8 +421,8 @@
       count_relocation_if<IsGeneral>(kRelocRelative);
       {
         const ElfW(Addr) result = sym_addr + get_addend_rel() - TLS_DTV_OFFSET;
-        trace_reloc("RELO TLS_DTPREL %16p <- %16p %s",
-                    rel_target, reinterpret_cast<void*>(result), sym_name);
+        LD_DEBUG(reloc && IsGeneral, "RELO TLS_DTPREL %16p <- %16p %s",
+                 rel_target, reinterpret_cast<void*>(result), sym_name);
         *static_cast<ElfW(Addr)*>(rel_target) = result;
       }
       break;
@@ -449,8 +439,8 @@
           // Unresolved weak relocation.
           desc->func = tlsdesc_resolver_unresolved_weak;
           desc->arg = addend;
-          trace_reloc("RELO TLSDESC %16p <- unresolved weak, addend 0x%zx %s",
-                      rel_target, static_cast<size_t>(addend), sym_name);
+          LD_DEBUG(reloc && IsGeneral, "RELO TLSDESC %16p <- unresolved weak, addend 0x%zx %s",
+                   rel_target, static_cast<size_t>(addend), sym_name);
         } else {
           CHECK(found_in->get_tls() != nullptr); // We rejected a missing TLS segment above.
           size_t module_id = found_in->get_tls()->module_id;
@@ -458,10 +448,10 @@
           if (mod.static_offset != SIZE_MAX) {
             desc->func = tlsdesc_resolver_static;
             desc->arg = mod.static_offset - relocator.tls_tp_base + sym_addr + addend;
-            trace_reloc("RELO TLSDESC %16p <- static (0x%zx - 0x%zx + 0x%zx + 0x%zx) %s",
-                        rel_target, mod.static_offset, relocator.tls_tp_base,
-                        static_cast<size_t>(sym_addr), static_cast<size_t>(addend),
-                        sym_name);
+            LD_DEBUG(reloc && IsGeneral, "RELO TLSDESC %16p <- static (0x%zx - 0x%zx + 0x%zx + 0x%zx) %s",
+                     rel_target, mod.static_offset, relocator.tls_tp_base,
+                     static_cast<size_t>(sym_addr), static_cast<size_t>(addend),
+                     sym_name);
           } else {
             relocator.tlsdesc_args->push_back({
               .generation = mod.first_generation,
@@ -474,9 +464,9 @@
               desc, relocator.tlsdesc_args->size() - 1
             });
             const TlsDynamicResolverArg& desc_arg = relocator.tlsdesc_args->back();
-            trace_reloc("RELO TLSDESC %16p <- dynamic (gen %zu, mod %zu, off %zu) %s",
-                        rel_target, desc_arg.generation, desc_arg.index.module_id,
-                        desc_arg.index.offset, sym_name);
+            LD_DEBUG(reloc && IsGeneral, "RELO TLSDESC %16p <- dynamic (gen %zu, mod %zu, off %zu) %s",
+                     rel_target, desc_arg.generation, desc_arg.index.module_id,
+                     desc_arg.index.offset, sym_name);
           }
         }
       }
@@ -488,8 +478,8 @@
       count_relocation_if<IsGeneral>(kRelocAbsolute);
       {
         const Elf32_Addr result = sym_addr + reloc.r_addend;
-        trace_reloc("RELO R_X86_64_32 %16p <- 0x%08x %s",
-                    rel_target, result, sym_name);
+        LD_DEBUG(reloc && IsGeneral, "RELO R_X86_64_32 %16p <- 0x%08x %s",
+                 rel_target, result, sym_name);
         *static_cast<Elf32_Addr*>(rel_target) = result;
       }
       break;
@@ -499,9 +489,9 @@
         const ElfW(Addr) target = sym_addr + reloc.r_addend;
         const ElfW(Addr) base = reinterpret_cast<ElfW(Addr)>(rel_target);
         const Elf32_Addr result = target - base;
-        trace_reloc("RELO R_X86_64_PC32 %16p <- 0x%08x (%16p - %16p) %s",
-                    rel_target, result, reinterpret_cast<void*>(target),
-                    reinterpret_cast<void*>(base), sym_name);
+        LD_DEBUG(reloc && IsGeneral, "RELO R_X86_64_PC32 %16p <- 0x%08x (%16p - %16p) %s",
+                 rel_target, result, reinterpret_cast<void*>(target),
+                 reinterpret_cast<void*>(base), sym_name);
         *static_cast<Elf32_Addr*>(rel_target) = result;
       }
       break;
@@ -512,9 +502,9 @@
         const ElfW(Addr) target = sym_addr + get_addend_rel();
         const ElfW(Addr) base = reinterpret_cast<ElfW(Addr)>(rel_target);
         const ElfW(Addr) result = target - base;
-        trace_reloc("RELO R_386_PC32 %16p <- 0x%08x (%16p - %16p) %s",
-                    rel_target, result, reinterpret_cast<void*>(target),
-                    reinterpret_cast<void*>(base), sym_name);
+        LD_DEBUG(reloc && IsGeneral, "RELO R_386_PC32 %16p <- 0x%08x (%16p - %16p) %s",
+                 rel_target, result, reinterpret_cast<void*>(target),
+                 reinterpret_cast<void*>(base), sym_name);
         *static_cast<ElfW(Addr)*>(rel_target) = result;
       }
       break;
@@ -559,15 +549,11 @@
 }
 
 static bool needs_slow_relocate_loop(const Relocator& relocator __unused) {
-#if STATS
-  // TODO: This could become a run-time flag.
-  return true;
-#endif
 #if !defined(__LP64__)
   if (relocator.si->has_text_relocations) return true;
 #endif
-  if (g_ld_debug_verbosity > LINKER_VERBOSITY_TRACE) {
-    // If linker TRACE() is enabled, then each relocation is logged.
+  // Both LD_DEBUG relocation logging and statistics need the slow path.
+  if (g_linker_debug_config.any || g_linker_debug_config.statistics) {
     return true;
   }
   return false;
@@ -611,7 +597,7 @@
   // The linker already applied its RELR relocations in an earlier pass, so
   // skip the RELR relocations for the linker.
   if (relr_ != nullptr && !is_linker()) {
-    DEBUG("[ relocating %s relr ]", get_realpath());
+    LD_DEBUG(reloc, "[ relocating %s relr ]", get_realpath());
     const ElfW(Relr)* begin = relr_;
     const ElfW(Relr)* end = relr_ + relr_count_;
     if (!relocate_relr(begin, end, load_bias)) {
@@ -626,7 +612,7 @@
         android_relocs_[1] == 'P' &&
         android_relocs_[2] == 'S' &&
         android_relocs_[3] == '2') {
-      DEBUG("[ relocating %s android rel/rela ]", get_realpath());
+      LD_DEBUG(reloc, "[ relocating %s android rel/rela ]", get_realpath());
 
       const uint8_t* packed_relocs = android_relocs_ + 4;
       const size_t packed_relocs_size = android_relocs_size_ - 4;
@@ -642,27 +628,27 @@
 
 #if defined(USE_RELA)
   if (rela_ != nullptr) {
-    DEBUG("[ relocating %s rela ]", get_realpath());
+    LD_DEBUG(reloc, "[ relocating %s rela ]", get_realpath());
 
     if (!plain_relocate<RelocMode::Typical>(relocator, rela_, rela_count_)) {
       return false;
     }
   }
   if (plt_rela_ != nullptr) {
-    DEBUG("[ relocating %s plt rela ]", get_realpath());
+    LD_DEBUG(reloc, "[ relocating %s plt rela ]", get_realpath());
     if (!plain_relocate<RelocMode::JumpTable>(relocator, plt_rela_, plt_rela_count_)) {
       return false;
     }
   }
 #else
   if (rel_ != nullptr) {
-    DEBUG("[ relocating %s rel ]", get_realpath());
+    LD_DEBUG(reloc, "[ relocating %s rel ]", get_realpath());
     if (!plain_relocate<RelocMode::Typical>(relocator, rel_, rel_count_)) {
       return false;
     }
   }
   if (plt_rel_ != nullptr) {
-    DEBUG("[ relocating %s plt rel ]", get_realpath());
+   LD_DEBUG(reloc, "[ relocating %s plt rel ]", get_realpath());
     if (!plain_relocate<RelocMode::JumpTable>(relocator, plt_rel_, plt_rel_count_)) {
       return false;
     }
diff --git a/linker/linker_soinfo.cpp b/linker/linker_soinfo.cpp
index b2170d8..0549d36 100644
--- a/linker/linker_soinfo.cpp
+++ b/linker/linker_soinfo.cpp
@@ -45,20 +45,15 @@
 #include "linker_relocate.h"
 #include "linker_utils.h"
 
-// Enable the slow lookup path if symbol lookups should be logged.
-static bool is_lookup_tracing_enabled() {
-  return g_ld_debug_verbosity > LINKER_VERBOSITY_TRACE && DO_TRACE_LOOKUP;
-}
-
 SymbolLookupList::SymbolLookupList(soinfo* si)
     : sole_lib_(si->get_lookup_lib()), begin_(&sole_lib_), end_(&sole_lib_ + 1) {
   CHECK(si != nullptr);
-  slow_path_count_ += is_lookup_tracing_enabled();
+  slow_path_count_ += !!g_linker_debug_config.lookup;
   slow_path_count_ += sole_lib_.needs_sysv_lookup();
 }
 
 SymbolLookupList::SymbolLookupList(const soinfo_list_t& global_group, const soinfo_list_t& local_group) {
-  slow_path_count_ += is_lookup_tracing_enabled();
+  slow_path_count_ += !!g_linker_debug_config.lookup;
   libs_.reserve(1 + global_group.size() + local_group.size());
 
   // Reserve a space in front for DT_SYMBOLIC lookup.
@@ -144,8 +139,8 @@
       }
 
       if (IsGeneral) {
-        TRACE_TYPE(LOOKUP, "SEARCH %s in %s@%p (gnu)",
-                   name, lib->si_->get_realpath(), reinterpret_cast<void*>(lib->si_->base));
+        LD_DEBUG(lookup, "SEARCH %s in %s@%p (gnu)",
+                 name, lib->si_->get_realpath(), reinterpret_cast<void*>(lib->si_->base));
       }
 
       const uint32_t word_num = (hash / kBloomMaskBits) & lib->gnu_maskwords_;
@@ -318,8 +313,8 @@
   const uint32_t h1 = hash % kBloomMaskBits;
   const uint32_t h2 = (hash >> gnu_shift2_) % kBloomMaskBits;
 
-  TRACE_TYPE(LOOKUP, "SEARCH %s in %s@%p (gnu)",
-      symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(base));
+  LD_DEBUG(lookup, "SEARCH %s in %s@%p (gnu)",
+           symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(base));
 
   // test against bloom filter
   if ((1 & (bloom_word >> h1) & (bloom_word >> h2)) == 0) {
@@ -352,9 +347,9 @@
 const ElfW(Sym)* soinfo::elf_lookup(SymbolName& symbol_name, const version_info* vi) const {
   uint32_t hash = symbol_name.elf_hash();
 
-  TRACE_TYPE(LOOKUP, "SEARCH %s in %s@%p h=%x(elf) %zd",
-             symbol_name.get_name(), get_realpath(),
-             reinterpret_cast<void*>(base), hash, hash % nbucket_);
+  LD_DEBUG(lookup, "SEARCH %s in %s@%p h=%x(elf) %zd",
+           symbol_name.get_name(), get_realpath(),
+           reinterpret_cast<void*>(base), hash, hash % nbucket_);
 
   const ElfW(Versym) verneed = find_verdef_version_index(this, vi);
   const ElfW(Versym)* versym = get_versym_table();
@@ -429,9 +424,9 @@
     return;
   }
 
-  TRACE("[ Calling c-tor %s @ %p for '%s' ]", function_name, function, realpath);
+  LD_DEBUG(calls, "[ Calling c-tor %s @ %p for '%s' ]", function_name, function, realpath);
   function(g_argc, g_argv, g_envp);
-  TRACE("[ Done calling c-tor %s @ %p for '%s' ]", function_name, function, realpath);
+  LD_DEBUG(calls, "[ Done calling c-tor %s @ %p for '%s' ]", function_name, function, realpath);
 }
 
 static void call_function(const char* function_name __unused,
@@ -441,9 +436,9 @@
     return;
   }
 
-  TRACE("[ Calling d-tor %s @ %p for '%s' ]", function_name, function, realpath);
+  LD_DEBUG(calls, "[ Calling d-tor %s @ %p for '%s' ]", function_name, function, realpath);
   function();
-  TRACE("[ Done calling d-tor %s @ %p for '%s' ]", function_name, function, realpath);
+  LD_DEBUG(calls, "[ Done calling d-tor %s @ %p for '%s' ]", function_name, function, realpath);
 }
 
 template <typename F>
@@ -453,18 +448,18 @@
     return;
   }
 
-  TRACE("[ Calling %s (size %zd) @ %p for '%s' ]", array_name, count, functions, realpath);
+  LD_DEBUG(calls, "[ Calling %s (size %zd) @ %p for '%s' ]", array_name, count, functions, realpath);
 
   int begin = reverse ? (count - 1) : 0;
   int end = reverse ? -1 : count;
   int step = reverse ? -1 : 1;
 
   for (int i = begin; i != end; i += step) {
-    TRACE("[ %s[%d] == %p ]", array_name, i, functions[i]);
+    LD_DEBUG(calls, "[ %s[%d] == %p ]", array_name, i, functions[i]);
     call_function("function", functions[i], realpath);
   }
 
-  TRACE("[ Done calling %s for '%s' ]", array_name, realpath);
+  LD_DEBUG(calls, "[ Done calling %s for '%s' ]", array_name, realpath);
 }
 
 void soinfo::call_pre_init_constructors() {
@@ -492,7 +487,7 @@
 
   if (!is_main_executable() && preinit_array_ != nullptr) {
     // The GNU dynamic linker silently ignores these, but we warn the developer.
-    PRINT("\"%s\": ignoring DT_PREINIT_ARRAY in shared library!", get_realpath());
+    DL_WARN("\"%s\": ignoring DT_PREINIT_ARRAY in shared library!", get_realpath());
   }
 
   get_children().for_each([] (soinfo* si) {
diff --git a/linker/linker_test_globals.cpp b/linker/linker_test_globals.cpp
index 4b41eed..27ec6f7 100644
--- a/linker/linker_test_globals.cpp
+++ b/linker/linker_test_globals.cpp
@@ -26,9 +26,6 @@
  * SUCH DAMAGE.
  */
 
-// To enable logging
-int g_ld_debug_verbosity = 0;
-
 // Stub some symbols to avoid linking issues
 void DL_WARN_documented_change(int api_level [[maybe_unused]],
                                const char* doc_link [[maybe_unused]],
diff --git a/linker/linker_translate_path.cpp b/linker/linker_translate_path.cpp
index 4f3fdfb..b41669e 100644
--- a/linker/linker_translate_path.cpp
+++ b/linker/linker_translate_path.cpp
@@ -42,13 +42,13 @@
 // Workaround for dlopen(/system/lib(64)/<soname>) when .so is in /apex. http://b/121248172
 /**
  * Translate /system path to /apex path if needed
- * The workaround should work only when targetSdkVersion < Q.
+ * The workaround should work only when targetSdkVersion < 29.
  *
  * param out_name_to_apex pointing to /apex path
  * return true if translation is needed
  */
 bool translateSystemPathToApexPath(const char* name, std::string* out_name_to_apex) {
-  static constexpr const char* kPathTranslationQ[][2] = {
+  static constexpr const char* kPathTranslation[][2] = {
       APEX_LIB("com.android.i18n", "libicui18n.so"),
       APEX_LIB("com.android.i18n", "libicuuc.so")
   };
@@ -59,10 +59,10 @@
 
   auto comparator = [name](auto p) { return strcmp(name, p[0]) == 0; };
 
-  if (get_application_target_sdk_version() < __ANDROID_API_Q__) {
+  if (get_application_target_sdk_version() < 29) {
     if (auto it =
-            std::find_if(std::begin(kPathTranslationQ), std::end(kPathTranslationQ), comparator);
-        it != std::end(kPathTranslationQ)) {
+            std::find_if(std::begin(kPathTranslation), std::end(kPathTranslation), comparator);
+        it != std::end(kPathTranslation)) {
       *out_name_to_apex = (*it)[1];
       return true;
     }
diff --git a/linker/linker_utils.cpp b/linker/linker_utils.cpp
index 9abe542..f72716e 100644
--- a/linker/linker_utils.cpp
+++ b/linker/linker_utils.cpp
@@ -75,7 +75,7 @@
 bool normalize_path(const char* path, std::string* normalized_path) {
   // Input should be an absolute path
   if (path[0] != '/') {
-    PRINT("normalize_path - invalid input: \"%s\", the input path should be absolute", path);
+    DL_WARN("normalize_path - invalid input: \"%s\", the input path should be absolute", path);
     return false;
   }
 
@@ -144,7 +144,7 @@
   }
 
   const char* const path = normalized_path.c_str();
-  TRACE("Trying zip file open from path \"%s\" -> normalized \"%s\"", input_path, path);
+  LD_DEBUG(any, "Trying zip file open from path \"%s\" -> normalized \"%s\"", input_path, path);
 
   // Treat an '!/' separator inside a path as the separator between the name
   // of the zip file on disk and the subdirectory to search within it.
@@ -157,7 +157,7 @@
 
   char buf[512];
   if (strlcpy(buf, path, sizeof(buf)) >= sizeof(buf)) {
-    PRINT("Warning: ignoring very long library path: %s", path);
+    DL_WARN("ignoring very long library path: %s", path);
     return false;
   }
 
diff --git a/tests/grp_pwd_test.cpp b/tests/grp_pwd_test.cpp
index ddc0fc1..7b7e0e5 100644
--- a/tests/grp_pwd_test.cpp
+++ b/tests/grp_pwd_test.cpp
@@ -444,10 +444,9 @@
     return result;
   };
 
-  // AID_UPROBESTATS (1093) was added in V, but "trunk stable" means
-  // that the 2024Q builds don't have branches like the QPR builds used
-  // to, and are tested with the _previous_ release's CTS.
-  if (android::base::GetIntProperty("ro.build.version.sdk", 0) == __ANDROID_API_U__) {
+  // AID_UPROBESTATS (1093) was added in API level 35, but "trunk stable" means
+  // that the 2024Q* builds are tested with the _previous_ release's CTS.
+  if (android::base::GetIntProperty("ro.build.version.sdk", 0) == 34) {
 #if !defined(AID_UPROBESTATS)
 #define AID_UPROBESTATS 1093
 #endif
@@ -457,10 +456,9 @@
       EXPECT_STREQ(getpwuid(AID_UPROBESTATS)->pw_name, "uprobestats");
     }
   }
-  // AID_VIRTUALMACHINE (3013) was added in V, but "trunk stable" means
-  // that the 2024Q builds don't have branches like the QPR builds used
-  // to, and are tested with the _previous_ release's CTS.
-  if (android::base::GetIntProperty("ro.build.version.sdk", 0) == __ANDROID_API_U__) {
+  // AID_VIRTUALMACHINE (3013) was added in API level 35, but "trunk stable" means
+  // that the 2024Q* builds are tested with the _previous_ release's CTS.
+  if (android::base::GetIntProperty("ro.build.version.sdk", 0) == 34) {
 #if !defined(AID_VIRTUALMACHINE)
 #define AID_VIRTUALMACHINE 3013
 #endif
@@ -470,6 +468,18 @@
       EXPECT_STREQ(getpwuid(AID_VIRTUALMACHINE)->pw_name, "virtualmachine");
     }
   }
+  // AID_CROS_EC (1094) was added in API level 36, but "trunk stable" means
+  // that the 2024Q* builds are tested with the _previous_ release's CTS.
+  if (android::base::GetIntProperty("ro.build.version.sdk", 0) == 35) {
+#if !defined(AID_CROS_EC)
+#define AID_CROS_EC 1094
+#endif
+    ids.erase(AID_CROS_EC);
+    expected_ids.erase(AID_CROS_EC);
+    if (getpwuid(AID_CROS_EC)) {
+      EXPECT_STREQ(getpwuid(AID_CROS_EC)->pw_name, "cros_ec");
+    }
+  }
 
   EXPECT_EQ(expected_ids, ids) << return_differences();
 }
diff --git a/tests/libs/Android.bp b/tests/libs/Android.bp
index fc7fd40..35f0f0c 100644
--- a/tests/libs/Android.bp
+++ b/tests/libs/Android.bp
@@ -1852,7 +1852,7 @@
         " cp $(in) $(genDir)/zipdir/libdir/ &&" +
         " touch $(genDir)/zipdir/empty_file.txt &&" +
         " $(location soong_zip) -o $(out).unaligned -L 0 -C $(genDir)/zipdir -D $(genDir)/zipdir &&" +
-        " $(location bionic_tests_zipalign) 4096 $(out).unaligned $(out)",
+        " $(location bionic_tests_zipalign) 16384 $(out).unaligned $(out)",
 
 }
 
@@ -1891,5 +1891,5 @@
         " cp $(location :libtest_dt_runpath_y) $(genDir)/zipdir/libdir/dt_runpath_y/$$PRIVATE_LIB_OR_LIB64 &&" +
         " touch $(genDir)/zipdir/empty_file.txt &&" +
         " $(location soong_zip) -o $(out).unaligned -L 0 -C $(genDir)/zipdir -D $(genDir)/zipdir &&" +
-        " $(location bionic_tests_zipalign) 4096 $(out).unaligned $(out)",
+        " $(location bionic_tests_zipalign) 16384 $(out).unaligned $(out)",
 }
diff --git a/tests/prebuilt-elf-files/arm64/libtest_invalid-rw_load_segment.so b/tests/prebuilt-elf-files/arm64/libtest_invalid-rw_load_segment.so
index 6463c6b..46af37f 100755
--- a/tests/prebuilt-elf-files/arm64/libtest_invalid-rw_load_segment.so
+++ b/tests/prebuilt-elf-files/arm64/libtest_invalid-rw_load_segment.so
Binary files differ
diff --git a/tests/prebuilt-elf-files/arm64/libtest_invalid-textrels.so b/tests/prebuilt-elf-files/arm64/libtest_invalid-textrels.so
index f83bbe4..c60b0d6 100755
--- a/tests/prebuilt-elf-files/arm64/libtest_invalid-textrels.so
+++ b/tests/prebuilt-elf-files/arm64/libtest_invalid-textrels.so
Binary files differ
diff --git a/tests/prebuilt-elf-files/arm64/libtest_invalid-textrels2.so b/tests/prebuilt-elf-files/arm64/libtest_invalid-textrels2.so
index fbf62c5..eb33692 100755
--- a/tests/prebuilt-elf-files/arm64/libtest_invalid-textrels2.so
+++ b/tests/prebuilt-elf-files/arm64/libtest_invalid-textrels2.so
Binary files differ
diff --git a/tests/prebuilt-elf-files/arm64/libtest_invalid-zero_shentsize.so b/tests/prebuilt-elf-files/arm64/libtest_invalid-zero_shentsize.so
index 4ffc7e8..c186b1d 100755
--- a/tests/prebuilt-elf-files/arm64/libtest_invalid-zero_shentsize.so
+++ b/tests/prebuilt-elf-files/arm64/libtest_invalid-zero_shentsize.so
Binary files differ
diff --git a/tests/prebuilt-elf-files/arm64/libtest_invalid-zero_shstrndx.so b/tests/prebuilt-elf-files/arm64/libtest_invalid-zero_shstrndx.so
index 9098310..857f702 100755
--- a/tests/prebuilt-elf-files/arm64/libtest_invalid-zero_shstrndx.so
+++ b/tests/prebuilt-elf-files/arm64/libtest_invalid-zero_shstrndx.so
Binary files differ
diff --git a/tests/prebuilt-elf-files/x86_64/libtest_invalid-rw_load_segment.so b/tests/prebuilt-elf-files/x86_64/libtest_invalid-rw_load_segment.so
index 113e455..9d2c5f1 100755
--- a/tests/prebuilt-elf-files/x86_64/libtest_invalid-rw_load_segment.so
+++ b/tests/prebuilt-elf-files/x86_64/libtest_invalid-rw_load_segment.so
Binary files differ
diff --git a/tests/prebuilt-elf-files/x86_64/libtest_invalid-textrels.so b/tests/prebuilt-elf-files/x86_64/libtest_invalid-textrels.so
index 719fb5a..f231d11 100755
--- a/tests/prebuilt-elf-files/x86_64/libtest_invalid-textrels.so
+++ b/tests/prebuilt-elf-files/x86_64/libtest_invalid-textrels.so
Binary files differ
diff --git a/tests/prebuilt-elf-files/x86_64/libtest_invalid-textrels2.so b/tests/prebuilt-elf-files/x86_64/libtest_invalid-textrels2.so
index 9d0741e..97fb5c4 100755
--- a/tests/prebuilt-elf-files/x86_64/libtest_invalid-textrels2.so
+++ b/tests/prebuilt-elf-files/x86_64/libtest_invalid-textrels2.so
Binary files differ
diff --git a/tests/prebuilt-elf-files/x86_64/libtest_invalid-zero_shentsize.so b/tests/prebuilt-elf-files/x86_64/libtest_invalid-zero_shentsize.so
index 78fed79..8146676 100755
--- a/tests/prebuilt-elf-files/x86_64/libtest_invalid-zero_shentsize.so
+++ b/tests/prebuilt-elf-files/x86_64/libtest_invalid-zero_shentsize.so
Binary files differ
diff --git a/tests/prebuilt-elf-files/x86_64/libtest_invalid-zero_shstrndx.so b/tests/prebuilt-elf-files/x86_64/libtest_invalid-zero_shstrndx.so
index 0953633..4ac70f7 100755
--- a/tests/prebuilt-elf-files/x86_64/libtest_invalid-zero_shstrndx.so
+++ b/tests/prebuilt-elf-files/x86_64/libtest_invalid-zero_shstrndx.so
Binary files differ