Merge "Enable arm64 .eh_frame terminator, align it to 4"
diff --git a/libc/Android.bp b/libc/Android.bp
index 61d99ac..c94be62 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -1738,6 +1738,10 @@
     // Sorting bss symbols by size usually results in less dirty pages at run
     // time, because small symbols are grouped together.
     sort_bss_symbols_by_size: true,
+
+    lto: {
+        never: true,
+    },
 }
 
 genrule {
diff --git a/libc/arch-arm64/bionic/__bionic_clone.S b/libc/arch-arm64/bionic/__bionic_clone.S
index c3ff0e5..e9932ad 100644
--- a/libc/arch-arm64/bionic/__bionic_clone.S
+++ b/libc/arch-arm64/bionic/__bionic_clone.S
@@ -57,3 +57,5 @@
     ldp     x0, x1, [sp], #16
     b       __start_thread
 END(__bionic_clone)
+
+NOTE_GNU_PROPERTY()
diff --git a/libc/arch-arm64/bionic/_exit_with_stack_teardown.S b/libc/arch-arm64/bionic/_exit_with_stack_teardown.S
index 6a7b1e5..c53a1f4 100644
--- a/libc/arch-arm64/bionic/_exit_with_stack_teardown.S
+++ b/libc/arch-arm64/bionic/_exit_with_stack_teardown.S
@@ -39,3 +39,5 @@
   svc #0
   // The exit syscall does not return.
 END(_exit_with_stack_teardown)
+
+NOTE_GNU_PROPERTY()
diff --git a/libc/arch-arm64/bionic/setjmp.S b/libc/arch-arm64/bionic/setjmp.S
index a2b2370..07270c9 100644
--- a/libc/arch-arm64/bionic/setjmp.S
+++ b/libc/arch-arm64/bionic/setjmp.S
@@ -118,6 +118,8 @@
 // int sigsetjmp(sigjmp_buf env, int save_signal_mask);
 ENTRY(sigsetjmp)
 __BIONIC_WEAK_ASM_FOR_NATIVE_BRIDGE(sigsetjmp)
+  hint #25 // paciasp
+  .cfi_negate_ra_state
   stp x0, x30, [sp, #-16]!
   .cfi_def_cfa_offset 16
   .cfi_rel_offset x0, 0
@@ -184,6 +186,8 @@
 #endif
 
   mov w0, #0
+  hint #29 // autiasp
+  .cfi_negate_ra_state
   ret
 END(sigsetjmp)
 
@@ -250,7 +254,9 @@
 1:
   // Restore core registers.
   bic x2, x2, #1
+  // x30 was saved with PAC to jmp_buf in sigsetjmp().
   ldp x30, x10, [x0, #(_JB_X30_SP  * 8)]
+  .cfi_negate_ra_state
   ldp x28, x29, [x0, #(_JB_X28_X29 * 8)]
   ldp x26, x27, [x0, #(_JB_X26_X27 * 8)]
   ldp x24, x25, [x0, #(_JB_X24_X25 * 8)]
@@ -290,6 +296,8 @@
   // Set return value.
   cmp w1, wzr
   csinc w0, w1, wzr, ne
+  hint #29 // autiasp
+  .cfi_negate_ra_state
   ret
 END(siglongjmp)
 
@@ -297,3 +305,5 @@
 __BIONIC_WEAK_ASM_FOR_NATIVE_BRIDGE(longjmp)
 ALIAS_SYMBOL(_longjmp, siglongjmp)
 __BIONIC_WEAK_ASM_FOR_NATIVE_BRIDGE(_longjmp)
+
+NOTE_GNU_PROPERTY()
diff --git a/libc/arch-arm64/bionic/syscall.S b/libc/arch-arm64/bionic/syscall.S
index 8389f98..9e6f68a 100644
--- a/libc/arch-arm64/bionic/syscall.S
+++ b/libc/arch-arm64/bionic/syscall.S
@@ -47,3 +47,5 @@
 
     ret
 END(syscall)
+
+NOTE_GNU_PROPERTY()
diff --git a/libc/arch-arm64/bionic/vfork.S b/libc/arch-arm64/bionic/vfork.S
index 5cfb8b0..81b84a3 100644
--- a/libc/arch-arm64/bionic/vfork.S
+++ b/libc/arch-arm64/bionic/vfork.S
@@ -67,6 +67,8 @@
 
     // Clean up stack shadow in the parent process.
     // https://github.com/google/sanitizers/issues/925
+    hint #25 // paciasp
+    .cfi_negate_ra_state
     stp x0, x30, [sp, #-16]!
     .cfi_adjust_cfa_offset 16
     .cfi_rel_offset x0, 0
@@ -79,9 +81,13 @@
     .cfi_adjust_cfa_offset -16
     .cfi_restore x0
     .cfi_restore x30
+    hint #29 // autiasp
+    .cfi_negate_ra_state
 
 #endif
 
 .L_exit:
     ret
 END(vfork)
+
+NOTE_GNU_PROPERTY()
diff --git a/libc/arch-arm64/generic/bionic/__memcpy_chk.S b/libc/arch-arm64/generic/bionic/__memcpy_chk.S
index a6eeca4..a8e9e83 100644
--- a/libc/arch-arm64/generic/bionic/__memcpy_chk.S
+++ b/libc/arch-arm64/generic/bionic/__memcpy_chk.S
@@ -43,3 +43,5 @@
 
   bl __memcpy_chk_fail
 END(__memcpy_chk)
+
+NOTE_GNU_PROPERTY()
diff --git a/libc/arch-arm64/generic/bionic/memcpy.S b/libc/arch-arm64/generic/bionic/memcpy.S
index baadb92..bc1945c 100644
--- a/libc/arch-arm64/generic/bionic/memcpy.S
+++ b/libc/arch-arm64/generic/bionic/memcpy.S
@@ -33,3 +33,5 @@
 ENTRY(__memcpy)
   #include "memcpy_base.S"
 END(__memcpy)
+
+NOTE_GNU_PROPERTY()
diff --git a/libc/arch-arm64/generic/bionic/memmove.S b/libc/arch-arm64/generic/bionic/memmove.S
index 335b7d6..0f752ea 100644
--- a/libc/arch-arm64/generic/bionic/memmove.S
+++ b/libc/arch-arm64/generic/bionic/memmove.S
@@ -153,3 +153,5 @@
 
 ALIAS_SYMBOL(memcpy, memmove)
 #endif
+
+NOTE_GNU_PROPERTY()
diff --git a/libc/arch-arm64/generic/bionic/memset.S b/libc/arch-arm64/generic/bionic/memset.S
index 12fc09d..19d3510 100644
--- a/libc/arch-arm64/generic/bionic/memset.S
+++ b/libc/arch-arm64/generic/bionic/memset.S
@@ -249,3 +249,5 @@
 	b	L(tail64)
 
 END(memset)
+
+NOTE_GNU_PROPERTY()
diff --git a/libc/arch-arm64/generic/bionic/wmemmove.S b/libc/arch-arm64/generic/bionic/wmemmove.S
index e4f67f7..b130530 100644
--- a/libc/arch-arm64/generic/bionic/wmemmove.S
+++ b/libc/arch-arm64/generic/bionic/wmemmove.S
@@ -28,3 +28,5 @@
 #define WMEMMOVE
 #include "memmove.S"
 #undef WMEMMOVE
+
+NOTE_GNU_PROPERTY()
diff --git a/libc/arch-arm64/static_function_dispatch.S b/libc/arch-arm64/static_function_dispatch.S
index 65a1492..161ece8 100644
--- a/libc/arch-arm64/static_function_dispatch.S
+++ b/libc/arch-arm64/static_function_dispatch.S
@@ -42,3 +42,5 @@
 FUNCTION_DELEGATE(strlen, __strlen_aarch64_mte)
 FUNCTION_DELEGATE(strrchr, __strrchr_aarch64_mte)
 FUNCTION_DELEGATE(strncmp, __strncmp_aarch64_mte)
+
+NOTE_GNU_PROPERTY()
diff --git a/libc/arch-common/bionic/crtbegin.c b/libc/arch-common/bionic/crtbegin.c
index b7043dc..1f8dfd2 100644
--- a/libc/arch-common/bionic/crtbegin.c
+++ b/libc/arch-common/bionic/crtbegin.c
@@ -49,7 +49,7 @@
 #define POST "; .size _start, .-_start"
 
 #if defined(__aarch64__)
-__asm__(PRE "mov x0,sp; b _start_main" POST);
+__asm__(PRE "/* BTI J */ hint #36; mov x0,sp; b _start_main" POST);
 #elif defined(__arm__)
 __asm__(PRE "mov r0,sp; b _start_main" POST);
 #elif defined(__i386__)
diff --git a/libc/arch-common/bionic/crtbrand.S b/libc/arch-common/bionic/crtbrand.S
index 34d6480..3d80d73 100644
--- a/libc/arch-common/bionic/crtbrand.S
+++ b/libc/arch-common/bionic/crtbrand.S
@@ -26,6 +26,12 @@
  * SUCH DAMAGE.
  */
 
+#if defined(__aarch64__)
+#include <private/bionic_asm_arm64.h>
+
+__bionic_asm_custom_note_gnu_section()
+#endif
+
   .section .note.android.ident,"a",%note
   .balign 4
   .type abitag, %object
diff --git a/libc/arch-common/bionic/crtend.S b/libc/arch-common/bionic/crtend.S
index 3af9883..9676db8 100644
--- a/libc/arch-common/bionic/crtend.S
+++ b/libc/arch-common/bionic/crtend.S
@@ -28,6 +28,12 @@
 
 #include "asm_multiarch.h"
 
+#if defined(__aarch64__)
+#include <private/bionic_asm_arm64.h>
+
+__bionic_asm_custom_note_gnu_section()
+#endif
+
 	.section .preinit_array, "aw"
 	ASM_ALIGN_TO_PTR_SIZE
 	ASM_PTR_SIZE(0)
diff --git a/libc/arch-common/bionic/crtend_so.S b/libc/arch-common/bionic/crtend_so.S
index 8bcf60c..5875acb 100644
--- a/libc/arch-common/bionic/crtend_so.S
+++ b/libc/arch-common/bionic/crtend_so.S
@@ -26,6 +26,12 @@
  * SUCH DAMAGE.
  */
 
+#if defined(__aarch64__)
+#include <private/bionic_asm_arm64.h>
+
+__bionic_asm_custom_note_gnu_section()
+#endif
+
 #if defined(__linux__) && defined(__ELF__)
 	.section .note.GNU-stack,"",%progbits
 #endif
diff --git a/libc/bionic/fork.cpp b/libc/bionic/fork.cpp
index 5bc7d3f..8c5cf2b 100644
--- a/libc/bionic/fork.cpp
+++ b/libc/bionic/fork.cpp
@@ -33,8 +33,8 @@
 #include "private/bionic_defs.h"
 #include "pthread_internal.h"
 
-__BIONIC_WEAK_FOR_NATIVE_BRIDGE
-extern "C" __LIBC_HIDDEN__ int __clone_for_fork() {
+__BIONIC_WEAK_FOR_NATIVE_BRIDGE_INLINE
+int __clone_for_fork() {
   pthread_internal_t* self = __get_thread();
 
   int result = clone(nullptr, nullptr, (CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD),
diff --git a/libc/include/bits/elf_arm64.h b/libc/include/bits/elf_arm64.h
index 6bb8384..9330d7b 100644
--- a/libc/include/bits/elf_arm64.h
+++ b/libc/include/bits/elf_arm64.h
@@ -89,4 +89,9 @@
 #define R_AARCH64_TLSDESC               1031    /* 16-byte descriptor: resolver func + arg. */
 #define R_AARCH64_IRELATIVE             1032
 
+/* Dynamic array tags */
+#define DT_AARCH64_BTI_PLT              0x70000001
+#define DT_AARCH64_PAC_PLT              0x70000003
+#define DT_AARCH64_VARIANT_PCS          0x70000005
+
 #endif /* _AARCH64_ELF_MACHDEP_H_ */
diff --git a/libc/malloc_debug/MapData.cpp b/libc/malloc_debug/MapData.cpp
index e8fbc54..ded81a2 100644
--- a/libc/malloc_debug/MapData.cpp
+++ b/libc/malloc_debug/MapData.cpp
@@ -116,14 +116,17 @@
     if (!get_val<ElfW(Word)>(entry, addr + offsetof(ElfW(Phdr), p_type), &phdr.p_type)) {
       return;
     }
+    if (!get_val<ElfW(Word)>(entry, addr + offsetof(ElfW(Phdr), p_flags), &phdr.p_flags)) {
+      return;
+    }
     if (!get_val<ElfW(Off)>(entry, addr + offsetof(ElfW(Phdr), p_offset), &phdr.p_offset)) {
       return;
     }
-    if (phdr.p_type == PT_LOAD && phdr.p_offset == entry->offset) {
+    if ((phdr.p_type == PT_LOAD) && (phdr.p_flags & PF_X) ) {
       if (!get_val<ElfW(Addr)>(entry, addr + offsetof(ElfW(Phdr), p_vaddr), &phdr.p_vaddr)) {
         return;
       }
-      entry->load_bias = phdr.p_vaddr;
+      entry->load_bias = phdr.p_vaddr - phdr.p_offset;
       return;
     }
     addr += sizeof(phdr);
diff --git a/libc/malloc_hooks/Android.bp b/libc/malloc_hooks/Android.bp
index 77b523e..487f3fb 100644
--- a/libc/malloc_hooks/Android.bp
+++ b/libc/malloc_hooks/Android.bp
@@ -70,6 +70,7 @@
     cflags: [
         "-Wall",
         "-Werror",
+        "-O1",  // FIXME: http://b/169206016 - issues with aligned_alloc and -O2
     ],
     test_suites: ["general-tests"],
 }
diff --git a/libc/private/bionic_asm.h b/libc/private/bionic_asm.h
index 6409563..6d4f7d5 100644
--- a/libc/private/bionic_asm.h
+++ b/libc/private/bionic_asm.h
@@ -35,6 +35,7 @@
 #define __bionic_asm_custom_entry(f)
 #define __bionic_asm_custom_end(f)
 #define __bionic_asm_function_type @function
+#define __bionic_asm_custom_note_gnu_section()
 
 #if defined(__aarch64__)
 #include <private/bionic_asm_arm64.h>
@@ -83,4 +84,7 @@
     .globl alias; \
     .equ alias, original
 
+#define NOTE_GNU_PROPERTY() \
+    __bionic_asm_custom_note_gnu_section()
+
 #endif
diff --git a/libc/private/bionic_asm_arm64.h b/libc/private/bionic_asm_arm64.h
index 463ca31..c11732a 100644
--- a/libc/private/bionic_asm_arm64.h
+++ b/libc/private/bionic_asm_arm64.h
@@ -41,3 +41,32 @@
 
 #undef __bionic_asm_function_type
 #define __bionic_asm_function_type %function
+
+#if defined(__ARM_FEATURE_BTI_DEFAULT)
+#define __bionic_asm_aarch64_feature_bti    (1 << 0)
+#undef __bionic_asm_custom_entry
+#define __bionic_asm_custom_entry(f)        hint #34  // BTI C
+#else
+#define __bionic_asm_aarch64_feature_bti    0
+#endif
+
+#if defined(__ARM_FEATURE_PAC_DEFAULT)
+#define __bionic_asm_aarch64_feature_pac    (1 << 1)
+#else
+#define __bionic_asm_aarch64_feature_pac    0
+#endif
+
+#undef __bionic_asm_custom_note_gnu_section
+#define __bionic_asm_custom_note_gnu_section() \
+    .pushsection .note.gnu.property, "a"; \
+    .balign 8; \
+    .long 4; \
+    .long 0x10; \
+    .long 0x5; /* NT_GNU_PROPERTY_TYPE_0 */ \
+    .asciz "GNU"; \
+    .long 0xc0000000; /* GNU_PROPERTY_AARCH64_FEATURE_1_AND */ \
+    .long 4; \
+    .long (__bionic_asm_aarch64_feature_pac | \
+           __bionic_asm_aarch64_feature_bti); \
+    .long 0; \
+    .popsection; \
diff --git a/libc/private/bionic_defs.h b/libc/private/bionic_defs.h
index 1d4f86b..5a48f25 100644
--- a/libc/private/bionic_defs.h
+++ b/libc/private/bionic_defs.h
@@ -33,7 +33,15 @@
  * This label is used to mark libc/libdl symbols that may need to be replaced
  * by native bridge implementation.
  */
+#ifdef __ANDROID_NATIVE_BRIDGE__
 #define __BIONIC_WEAK_FOR_NATIVE_BRIDGE __attribute__((__weak__, __noinline__))
 #define __BIONIC_WEAK_VARIABLE_FOR_NATIVE_BRIDGE __attribute__((__weak__))
+#define __BIONIC_WEAK_FOR_NATIVE_BRIDGE_INLINE \
+  __BIONIC_WEAK_FOR_NATIVE_BRIDGE extern "C" __LIBC_HIDDEN__
+#else
+#define __BIONIC_WEAK_FOR_NATIVE_BRIDGE
+#define __BIONIC_WEAK_VARIABLE_FOR_NATIVE_BRIDGE
+#define __BIONIC_WEAK_FOR_NATIVE_BRIDGE_INLINE static inline
+#endif
 
 #endif /* __BIONIC_PRIVATE_BIONIC_DEFS_H_ */
diff --git a/libc/tools/gensyscalls.py b/libc/tools/gensyscalls.py
index 0271a04..0e0e25f 100755
--- a/libc/tools/gensyscalls.py
+++ b/libc/tools/gensyscalls.py
@@ -459,6 +459,8 @@
         if syscall.has_key("asm-%s" % arch):
             print(syscall["asm-%s" % arch])
 
+    if arch == 'arm64':
+        print('\nNOTE_GNU_PROPERTY()\n')
 
 if __name__ == "__main__":
     if len(sys.argv) < 2:
diff --git a/libdl/Android.bp b/libdl/Android.bp
index d843c44..1a5439f 100644
--- a/libdl/Android.bp
+++ b/libdl/Android.bp
@@ -124,6 +124,10 @@
         "//apex_available:platform",
         "com.android.runtime",
     ],
+
+    lto: {
+        never: true,
+    },
 }
 
 cc_library {
@@ -200,6 +204,10 @@
         "//apex_available:platform",
         "com.android.runtime",
     ],
+
+    lto: {
+        never: true,
+    },
 }
 
 ndk_library {
diff --git a/libm/Android.bp b/libm/Android.bp
index 318a4bc..7f96975 100644
--- a/libm/Android.bp
+++ b/libm/Android.bp
@@ -506,6 +506,10 @@
         "//apex_available:platform",
         "com.android.runtime",
     ],
+
+    lto: {
+        never: true,
+    },
 }
 
 ndk_library {
diff --git a/libm/arm64/lrint.S b/libm/arm64/lrint.S
index 5f95ae8..e835d08 100644
--- a/libm/arm64/lrint.S
+++ b/libm/arm64/lrint.S
@@ -32,3 +32,5 @@
 ALIAS_SYMBOL(llrint, lrint);
 
 ALIAS_SYMBOL(llrintf, lrintf);
+
+NOTE_GNU_PROPERTY()
diff --git a/libm/arm64/sqrt.S b/libm/arm64/sqrt.S
index 3a58ef3..0659b13 100644
--- a/libm/arm64/sqrt.S
+++ b/libm/arm64/sqrt.S
@@ -25,3 +25,5 @@
   fsqrt s0, s0
   ret
 END(sqrtf)
+
+NOTE_GNU_PROPERTY()
diff --git a/linker/Android.bp b/linker/Android.bp
index a51b73f..15585a7 100644
--- a/linker/Android.bp
+++ b/linker/Android.bp
@@ -169,6 +169,7 @@
         "linker_namespaces.cpp",
         "linker_logger.cpp",
         "linker_mapped_file_fragment.cpp",
+        "linker_note_gnu_property.cpp",
         "linker_phdr.cpp",
         "linker_relocate.cpp",
         "linker_sdk_versions.cpp",
@@ -356,6 +357,10 @@
             ],
         }
     },
+
+    lto: {
+        never: true,
+    },
 }
 
 // ========================================================
@@ -426,6 +431,10 @@
         "//apex_available:platform",
         "com.android.runtime",
     ],
+
+    lto: {
+        never: true,
+    },
 }
 
 cc_test {
@@ -447,6 +456,7 @@
         "linker_block_allocator_test.cpp",
         "linker_config_test.cpp",
         "linked_list_test.cpp",
+        "linker_note_gnu_property_test.cpp",
         "linker_sleb128_test.cpp",
         "linker_utils_test.cpp",
         "linker_gnu_hash_test.cpp",
@@ -455,6 +465,7 @@
         "linker_block_allocator.cpp",
         "linker_config.cpp",
         "linker_debug.cpp",
+        "linker_note_gnu_property.cpp",
         "linker_test_globals.cpp",
         "linker_utils.cpp",
     ],
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 302e4b3..77f754c 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -3141,6 +3141,14 @@
         // resolves everything eagerly, so these can be ignored.
         break;
 
+#if defined(__aarch64__)
+      case DT_AARCH64_BTI_PLT:
+      case DT_AARCH64_PAC_PLT:
+      case DT_AARCH64_VARIANT_PCS:
+        // Ignored: AArch64 processor-specific dynamic array tags.
+        break;
+#endif
+
       default:
         if (!relocating_linker) {
           const char* tag_name;
diff --git a/linker/linker.h b/linker/linker.h
index 3e851da..e1775fb 100644
--- a/linker/linker.h
+++ b/linker/linker.h
@@ -181,3 +181,9 @@
 int get_application_target_sdk_version();
 ElfW(Versym) find_verdef_version_index(const soinfo* si, const version_info* vi);
 bool validate_verdef_section(const soinfo* si);
+
+struct platform_properties {
+#if defined(__aarch64__)
+  bool bti_supported = false;
+#endif
+};
diff --git a/linker/linker_globals.cpp b/linker/linker_globals.cpp
index 31da02c..4a17d09 100644
--- a/linker/linker_globals.cpp
+++ b/linker/linker_globals.cpp
@@ -40,6 +40,8 @@
 
 std::unordered_map<uintptr_t, soinfo*> g_soinfo_handles_map;
 
+platform_properties g_platform_properties;
+
 static char __linker_dl_err_buf[768];
 
 char* linker_get_error_buffer() {
diff --git a/linker/linker_globals.h b/linker/linker_globals.h
index 83cedca..0998629 100644
--- a/linker/linker_globals.h
+++ b/linker/linker_globals.h
@@ -79,11 +79,14 @@
 
 struct soinfo;
 struct android_namespace_t;
+struct platform_properties;
 
 extern android_namespace_t g_default_namespace;
 
 extern std::unordered_map<uintptr_t, soinfo*> g_soinfo_handles_map;
 
+extern platform_properties g_platform_properties;
+
 // Error buffer "variable"
 char* linker_get_error_buffer();
 size_t linker_get_error_buffer_size();
diff --git a/linker/linker_main.cpp b/linker/linker_main.cpp
index 41bb4ba..aad8f6f 100644
--- a/linker/linker_main.cpp
+++ b/linker/linker_main.cpp
@@ -297,6 +297,13 @@
   return result;
 }
 
+static void platform_properties_init() {
+#if defined(__aarch64__)
+  const unsigned long hwcap2 = getauxval(AT_HWCAP2);
+  g_platform_properties.bti_supported = (hwcap2 & HWCAP2_BTI) != 0;
+#endif
+}
+
 static ElfW(Addr) linker_main(KernelArgumentBlock& args, const char* exe_to_load) {
   ProtectedDataGuard guard;
 
@@ -311,6 +318,9 @@
   // Initialize system properties
   __system_properties_init(); // may use 'environ'
 
+  // Initialize platform properties.
+  platform_properties_init();
+
   // Register the debuggerd signal handler.
   linker_debuggerd_init();
 
@@ -381,6 +391,20 @@
   solinker->set_realpath(interp);
   init_link_map_head(*solinker);
 
+#if defined(__aarch64__)
+  if (exe_to_load == nullptr) {
+    // Kernel does not add PROT_BTI to executable pages of the loaded ELF.
+    // Apply appropriate protections here if it is needed.
+    auto note_gnu_property = GnuPropertySection(somain);
+    if (note_gnu_property.IsBTICompatible() &&
+        (phdr_table_protect_segments(somain->phdr, somain->phnum, somain->load_bias,
+                                     &note_gnu_property) < 0)) {
+      __linker_error("error: can't protect segments for \"%s\": %s", exe_info.path.c_str(),
+                     strerror(errno));
+    }
+  }
+#endif
+
   // Register the main executable and the linker upfront to have
   // gdb aware of them before loading the rest of the dependency
   // tree.
diff --git a/linker/linker_note_gnu_property.cpp b/linker/linker_note_gnu_property.cpp
new file mode 100644
index 0000000..be1aebc
--- /dev/null
+++ b/linker/linker_note_gnu_property.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "linker_note_gnu_property.h"
+
+#include <elf.h>
+#include <link.h>
+
+#include "linker.h"
+#include "linker_debug.h"
+#include "linker_globals.h"
+#include "linker_soinfo.h"
+
+GnuPropertySection::GnuPropertySection(const soinfo* si)
+    : GnuPropertySection(si->phdr, si->phnum, si->load_bias, si->get_realpath()) {}
+
+GnuPropertySection::GnuPropertySection(const ElfW(Phdr)* phdr, size_t phdr_count,
+                                       const ElfW(Addr) load_bias, const char* name) {
+  // Try to find PT_GNU_PROPERTY segment.
+  auto note_gnu_property = FindSegment(phdr, phdr_count, load_bias, name);
+  // Perform some validity checks.
+  if (note_gnu_property && SanityCheck(note_gnu_property, name)) {
+    // Parse section.
+    Parse(note_gnu_property, name);
+  }
+}
+
+const ElfW(NhdrGNUProperty)* GnuPropertySection::FindSegment(const ElfW(Phdr)* phdr,
+                                                             size_t phdr_count,
+                                                             const ElfW(Addr) load_bias,
+                                                             const char* name) const {
+  // According to Linux gABI extension this segment should contain
+  // .note.gnu.property section only.
+  if (phdr != nullptr) {
+    for (size_t i = 0; i < phdr_count; ++i) {
+      if (phdr[i].p_type != PT_GNU_PROPERTY) {
+        continue;
+      }
+
+      TRACE("\"%s\" PT_GNU_PROPERTY: found at segment index %zu", name, i);
+
+      // Check segment size.
+      if (phdr[i].p_memsz < sizeof(ElfW(NhdrGNUProperty))) {
+        DL_ERR_AND_LOG(
+            "\"%s\" PT_GNU_PROPERTY segment is too small. Segment "
+            "size is %zu, minimum is %zu.",
+            name, static_cast<size_t>(phdr[i].p_memsz), sizeof(ElfW(NhdrGNUProperty)));
+        return nullptr;
+      }
+
+      // PT_GNU_PROPERTY contains .note.gnu.property which has SHF_ALLOC
+      // attribute, therefore it is loaded.
+      auto note_nhdr = reinterpret_cast<ElfW(NhdrGNUProperty)*>(load_bias + phdr[i].p_vaddr);
+
+      // Check that the n_descsz <= p_memsz
+      if ((phdr[i].p_memsz - sizeof(ElfW(NhdrGNUProperty))) < note_nhdr->nhdr.n_descsz) {
+        DL_ERR_AND_LOG(
+            "\"%s\" PT_GNU_PROPERTY segment p_memsz (%zu) is too small for note n_descsz (%zu).",
+            name, static_cast<size_t>(phdr[i].p_memsz),
+            static_cast<size_t>(note_nhdr->nhdr.n_descsz));
+        return nullptr;
+      }
+
+      return note_nhdr;
+    }
+  }
+
+  TRACE("\"%s\" PT_GNU_PROPERTY: not found", name);
+  return nullptr;
+}
+
+bool GnuPropertySection::SanityCheck(const ElfW(NhdrGNUProperty)* note_nhdr,
+                                     const char* name) const {
+  // Check .note section type
+  if (note_nhdr->nhdr.n_type != NT_GNU_PROPERTY_TYPE_0) {
+    DL_ERR_AND_LOG("\"%s\" .note.gnu.property: unexpected note type. Expected %u, got %u.", name,
+                   NT_GNU_PROPERTY_TYPE_0, note_nhdr->nhdr.n_type);
+    return false;
+  }
+
+  if (note_nhdr->nhdr.n_namesz != 4) {
+    DL_ERR_AND_LOG("\"%s\" .note.gnu.property: unexpected name size. Expected 4, got %u.", name,
+                   note_nhdr->nhdr.n_namesz);
+    return false;
+  }
+
+  if (strncmp(note_nhdr->n_name, "GNU", 4) != 0) {
+    DL_ERR_AND_LOG("\"%s\" .note.gnu.property: unexpected name. Expected 'GNU', got '%s'.", name,
+                   note_nhdr->n_name);
+    return false;
+  }
+
+  return true;
+}
+
+bool GnuPropertySection::Parse(const ElfW(NhdrGNUProperty)* note_nhdr, const char* name) {
+  // 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);
+
+    // At least the "header" part must fit.
+    // The ABI doesn't say that pr_datasz can't be 0.
+    if ((note_nhdr->nhdr.n_descsz - offset) < sizeof(ElfW(Prop))) {
+      DL_ERR_AND_LOG(
+          "\"%s\" .note.gnu.property: no more space left for a "
+          "Program Property Note header.",
+          name);
+      return false;
+    }
+
+    // Loop on program property array.
+    const ElfW(Prop)* property = reinterpret_cast<const ElfW(Prop)*>(&note_nhdr->n_desc[offset]);
+    const ElfW(Word) property_size =
+        align_up(sizeof(ElfW(Prop)) + property->pr_datasz, sizeof(ElfW(Addr)));
+    if ((note_nhdr->nhdr.n_descsz - offset) < property_size) {
+      DL_ERR_AND_LOG(
+          "\"%s\" .note.gnu.property: property descriptor size is "
+          "invalid. Expected at least %u bytes, got %u.",
+          name, property_size, note_nhdr->nhdr.n_descsz - offset);
+      return false;
+    }
+
+    // Cache found properties.
+    switch (property->pr_type) {
+#if defined(__aarch64__)
+      case GNU_PROPERTY_AARCH64_FEATURE_1_AND: {
+        if (property->pr_datasz != 4) {
+          DL_ERR_AND_LOG(
+              "\"%s\" .note.gnu.property: property descriptor size is "
+              "invalid. Expected %u bytes for GNU_PROPERTY_AARCH64_FEATURE_1_AND, got %u.",
+              name, 4, property->pr_datasz);
+          return false;
+        }
+
+        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);
+        }
+        break;
+      }
+#endif
+      default:
+        DEBUG("\"%s\" .note.gnu.property: found property pr_type %u pr_datasz 0x%x", name,
+              property->pr_type, property->pr_datasz);
+        break;
+    }
+
+    // Move offset, this should be safe to add because of previous checks.
+    offset += property_size;
+  }
+
+  return true;
+}
+
+#if defined(__aarch64__)
+bool GnuPropertySection::IsBTICompatible() const {
+  return (g_platform_properties.bti_supported && properties_.bti_compatible);
+}
+#endif
diff --git a/linker/linker_note_gnu_property.h b/linker/linker_note_gnu_property.h
new file mode 100644
index 0000000..b8b4ef7
--- /dev/null
+++ b/linker/linker_note_gnu_property.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <elf.h>
+#include <link.h>
+
+#include "linker_soinfo.h"
+
+// The Elf* structures below are derived from the document
+// Linux Extensions to gABI (https://github.com/hjl-tools/linux-abi/wiki).
+// Essentially, these types would be defined in <elf.h>, but this is not
+// the case at the moment.
+
+struct Elf32_Prop {
+  Elf32_Word pr_type;
+  Elf32_Word pr_datasz;
+  char pr_data[0];
+};
+
+// On 32-bit machines this should be 4-byte aligned.
+struct Elf32_NhdrGNUProperty {
+  Elf32_Nhdr nhdr;
+  char n_name[4];
+  char n_desc[0];
+};
+
+struct Elf64_Prop {
+  Elf64_Word pr_type;
+  Elf64_Word pr_datasz;
+  char pr_data[0];
+};
+
+// On 64-bit machines this should be 8-byte aligned.
+struct Elf64_NhdrGNUProperty {
+  Elf64_Nhdr nhdr;
+  char n_name[4];
+  char n_desc[0];
+};
+
+struct ElfProgramProperty {
+#if defined(__aarch64__)
+  bool bti_compatible = false;
+#endif
+};
+
+// Representation of the .note.gnu.property section found in the segment
+// with p_type = PT_GNU_PROPERTY.
+class GnuPropertySection {
+ public:
+  GnuPropertySection(){};
+  explicit GnuPropertySection(const soinfo* si);
+  GnuPropertySection(const ElfW(Phdr)* phdr, size_t phdr_count, const ElfW(Addr) load_bias,
+                     const char* name);
+
+#if defined(__aarch64__)
+  bool IsBTICompatible() const;
+#endif
+
+ private:
+  const ElfW(NhdrGNUProperty)* FindSegment(const ElfW(Phdr)* phdr, size_t phdr_count,
+                                           const ElfW(Addr) load_bias, const char* name) const;
+  bool SanityCheck(const ElfW(NhdrGNUProperty)* note_nhdr, const char* name) const;
+  bool Parse(const ElfW(NhdrGNUProperty)* note_nhdr, const char* name);
+
+  ElfProgramProperty properties_ __unused;
+};
diff --git a/linker/linker_note_gnu_property_test.cpp b/linker/linker_note_gnu_property_test.cpp
new file mode 100644
index 0000000..41fc47b
--- /dev/null
+++ b/linker/linker_note_gnu_property_test.cpp
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include "linker.h"
+#include "linker_globals.h"
+#include "linker_note_gnu_property.h"
+#include "platform/bionic/macros.h"
+
+#define SONAME "test_so"
+
+static char error_buffer[1024];
+
+char* linker_get_error_buffer() {
+  return error_buffer;
+}
+
+size_t linker_get_error_buffer_size() {
+  return std::size(error_buffer);
+}
+
+static void reset_error_buffer() {
+  error_buffer[0] = '\0';
+}
+
+platform_properties g_platform_properties {
+#if defined(__aarch64__)
+  // Assume "hardware" supports Armv8.5-A BTI.
+  .bti_supported = true
+#endif
+};
+
+// Helper macro to make the test cleaner.
+#define PHDR_WITH_NOTE_GNU_PROPERTY(__prop)                                   \
+  reset_error_buffer();                                                       \
+  ElfW(Phdr) phdrs[] = {                                                      \
+      {.p_type = PT_LOAD},                                                    \
+      {                                                                       \
+          .p_type = PT_GNU_PROPERTY,                                          \
+          .p_vaddr = reinterpret_cast<ElfW(Addr)>(__prop),                    \
+          .p_memsz = sizeof(ElfW(NhdrGNUProperty)) + (__prop)->nhdr.n_descsz, \
+      },                                                                      \
+      {.p_type = PT_NULL},                                                    \
+  };                                                                          \
+  auto note = GnuPropertySection(&phdrs[0], std::size(phdrs), 0, SONAME)
+
+// Helper to check for no error message.
+#define ASSERT_NO_ERROR_MSG() ASSERT_STREQ(error_buffer, "")
+
+// Helper to check expected error message.
+#define ASSERT_ERROR_MSG_EQ(__expected) ASSERT_STREQ(error_buffer, "\"" SONAME "\" " __expected)
+
+static void test_bti_not_supported(GnuPropertySection& note __unused) {
+#if defined(__aarch64__)
+  ASSERT_FALSE(note.IsBTICompatible());
+#endif
+}
+
+#if defined(__aarch64__)
+static void test_bti_supported(GnuPropertySection& note __unused) {
+  ASSERT_TRUE(note.IsBTICompatible());
+}
+#endif
+
+// Helper class to build a well-formed .note.gnu.property section.
+class GnuPropertySectionBuilder {
+ public:
+  GnuPropertySectionBuilder() {
+    note = reinterpret_cast<ElfW(NhdrGNUProperty)*>(&section[0]);
+    note->nhdr.n_namesz = 4;
+    note->nhdr.n_descsz = 0;
+    note->nhdr.n_type = NT_GNU_PROPERTY_TYPE_0;
+    memcpy(note->n_name, "GNU", 4);
+  }
+
+  template <typename T>
+  bool push(ElfW(Word) pr_type, ElfW(Word) pr_datasz, const T* pr_data) {
+    // Must be aligned.
+    const uintptr_t addition = align_up(pr_datasz, sizeof(ElfW(Addr)));
+    if ((offset() + addition) > kMaxSectionSize) {
+      return false;
+    }
+    ++entries;
+    ElfW(Prop)* prop = reinterpret_cast<ElfW(Prop)*>(&section[offset()]);
+    // Header
+    prop->pr_type = pr_type;
+    prop->pr_datasz = pr_datasz;
+    step(2 * sizeof(ElfW(Word)));
+    // Data
+    memcpy(&section[offset()], reinterpret_cast<const void*>(pr_data), pr_datasz);
+    step(pr_datasz);
+    // Padding
+    memset(&section[offset()], 0xAA, addition - pr_datasz);
+    step(addition - pr_datasz);
+    return true;
+  }
+
+  ElfW(NhdrGNUProperty)* data() const { return note; }
+
+  void dump() const {
+    std::cout << ".note.gnu.property\n";
+    dump_member("n_namesz", note->nhdr.n_namesz);
+    dump_member("n_descsz", note->nhdr.n_descsz);
+    dump_member("n_type  ", note->nhdr.n_type);
+    dump_member("n_name  ", note->n_name);
+    dump_member("entries ", entries);
+    if (entries > 0) {
+      std::cout << "    raw data:";
+      const uintptr_t offset = note->nhdr.n_descsz + 16;
+      for (uintptr_t offs = 16; offs < offset; ++offs) {
+        std::cout << std::hex;
+        if ((offs % 8) == 0) {
+          std::cout << "\n   ";
+        }
+        auto value = static_cast<unsigned>(section[offs]);
+        std::cout << " ";
+        if (value < 0x10) {
+          std::cout << "0";
+        }
+        std::cout << static_cast<unsigned>(section[offs]);
+      }
+      std::cout << std::dec << "\n";
+    }
+  }
+
+  void corrupt_n_descsz(ElfW(Word) n_descsz) { note->nhdr.n_descsz = n_descsz; }
+
+ private:
+  template <typename T>
+  void dump_member(const char* name, T value) const {
+    std::cout << "  " << name << " " << value << "\n";
+  }
+
+  ElfW(Word) offset() const { return note->nhdr.n_descsz + 16; }
+
+  template <typename T>
+  void step(T value) {
+    note->nhdr.n_descsz += static_cast<ElfW(Word)>(value);
+  }
+
+  static const size_t kMaxSectionSize = 1024;
+
+  alignas(8) uint8_t section[kMaxSectionSize];
+  ElfW(NhdrGNUProperty)* note;
+  size_t entries = 0;
+};
+
+// Tests that the default constructed instance does not report support
+// for Armv8.5-A BTI.
+TEST(note_gnu_property, default) {
+  GnuPropertySection note;
+  test_bti_not_supported(note);
+  ASSERT_NO_ERROR_MSG();
+}
+
+// Tests that an instance without valid phdr pointer does not report
+// support for Armv8.5-A BTI.
+TEST(note_gnu_property, phdr_null) {
+  auto note = GnuPropertySection(nullptr, 0, 0, SONAME);
+  test_bti_not_supported(note);
+  ASSERT_NO_ERROR_MSG();
+}
+
+// Tests that an instance without finding PT_GNU_PROPERTY does not
+// report support for Armv8.5-A BTI.
+TEST(note_gnu_property, no_pt_gnu_property) {
+  ElfW(Phdr) phdrs[] = {
+      {.p_type = PT_LOAD},
+      {.p_type = PT_NULL},
+  };
+
+  reset_error_buffer();
+  auto note = GnuPropertySection(&phdrs[0], std::size(phdrs), 0, SONAME);
+  test_bti_not_supported(note);
+  ASSERT_NO_ERROR_MSG();
+}
+
+// Tests the validity check for invalid PT_GNU_PROPERTY size.
+TEST(note_gnu_property, pt_gnu_property_bad_size) {
+  ElfW(Phdr) phdrs[] = {
+      {.p_type = PT_LOAD},
+      {
+          .p_type = PT_GNU_PROPERTY,
+          .p_vaddr = 0,
+          .p_memsz = sizeof(ElfW(NhdrGNUProperty)) - 1,  // Invalid
+      },
+      {.p_type = PT_NULL},
+  };
+
+  reset_error_buffer();
+  auto note = GnuPropertySection(&phdrs[0], std::size(phdrs), 0, SONAME);
+  test_bti_not_supported(note);
+  ASSERT_ERROR_MSG_EQ("PT_GNU_PROPERTY segment is too small. Segment size is 15, minimum is 16.");
+}
+
+// Tests that advertised n_descsz should still fit into p_memsz.
+TEST(note_gnu_property, pt_gnu_property_too_small) {
+  ElfW(NhdrGNUProperty) prop = {
+      .nhdr = {.n_namesz = PT_GNU_PROPERTY, .n_descsz = 1, .n_type = NT_GNU_PROPERTY_TYPE_0},
+      .n_name = "GNU",
+  };
+  ElfW(Phdr) phdrs[] = {
+      {
+          .p_type = PT_GNU_PROPERTY,
+          .p_vaddr = reinterpret_cast<ElfW(Addr)>(&prop),
+          .p_memsz = sizeof(ElfW(NhdrGNUProperty)),  // Off by one
+      },
+  };
+
+  reset_error_buffer();
+  auto note = GnuPropertySection(&phdrs[0], std::size(phdrs), 0, SONAME);
+  test_bti_not_supported(note);
+  ASSERT_ERROR_MSG_EQ("PT_GNU_PROPERTY segment p_memsz (16) is too small for note n_descsz (1).");
+}
+
+// Tests the validity check for invalid .note.gnu.property type.
+TEST(note_gnu_property, pt_gnu_property_bad_type) {
+  ElfW(NhdrGNUProperty) prop = {
+      .nhdr =
+          {
+              .n_namesz = 4,
+              .n_descsz = 0,
+              .n_type = NT_GNU_PROPERTY_TYPE_0 - 1  // Invalid
+          },
+      .n_name = "GNU",
+  };
+  PHDR_WITH_NOTE_GNU_PROPERTY(&prop);
+  test_bti_not_supported(note);
+  ASSERT_ERROR_MSG_EQ(".note.gnu.property: unexpected note type. Expected 5, got 4.");
+}
+
+// Tests the validity check for invalid .note.gnu.property name size.
+TEST(note_gnu_property, pt_gnu_property_bad_namesz) {
+  ElfW(NhdrGNUProperty) prop = {
+      .nhdr = {.n_namesz = 3,  // Invalid
+               .n_descsz = 0,
+               .n_type = NT_GNU_PROPERTY_TYPE_0},
+      .n_name = "GNU",
+  };
+  PHDR_WITH_NOTE_GNU_PROPERTY(&prop);
+  test_bti_not_supported(note);
+  ASSERT_ERROR_MSG_EQ(".note.gnu.property: unexpected name size. Expected 4, got 3.");
+}
+
+// Tests the validity check for invalid .note.gnu.property name.
+TEST(note_gnu_property, pt_gnu_property_bad_name) {
+  ElfW(NhdrGNUProperty) prop = {
+      .nhdr = {.n_namesz = 4, .n_descsz = 0, .n_type = NT_GNU_PROPERTY_TYPE_0},
+      .n_name = "ABC",  // Invalid
+  };
+  PHDR_WITH_NOTE_GNU_PROPERTY(&prop);
+  test_bti_not_supported(note);
+  ASSERT_ERROR_MSG_EQ(".note.gnu.property: unexpected name. Expected 'GNU', got 'ABC'.");
+}
+
+// Tests the validity check for not enough space for a Program Property header.
+TEST(note_gnu_property, pt_gnu_property_pphdr_no_space) {
+  ElfW(NhdrGNUProperty) prop = {
+      .nhdr = {.n_namesz = 4,
+               .n_descsz = 7,  // Invalid
+               .n_type = NT_GNU_PROPERTY_TYPE_0},
+      .n_name = "GNU",
+  };
+  PHDR_WITH_NOTE_GNU_PROPERTY(&prop);
+  test_bti_not_supported(note);
+  ASSERT_ERROR_MSG_EQ(".note.gnu.property: no more space left for a Program Property Note header.");
+}
+
+// Tests an empty .note.gnu.property.
+TEST(note_gnu_property, pt_gnu_property_no_data) {
+  GnuPropertySectionBuilder prop;
+  PHDR_WITH_NOTE_GNU_PROPERTY(prop.data());
+  test_bti_not_supported(note);
+  ASSERT_NO_ERROR_MSG();
+}
+
+// Tests a .note.gnu.property section with elements with pr_datasz = 0.
+TEST(note_gnu_property, pt_gnu_property_no_prop) {
+  GnuPropertySectionBuilder prop;
+  ASSERT_TRUE(prop.push(1, 0, (void*)nullptr));
+  ASSERT_TRUE(prop.push(2, 0, (void*)nullptr));
+  ASSERT_TRUE(prop.push(3, 0, (void*)nullptr));
+  PHDR_WITH_NOTE_GNU_PROPERTY(prop.data());
+  test_bti_not_supported(note);
+  ASSERT_NO_ERROR_MSG();
+}
+
+// Tests that GNU_PROPERTY_AARCH64_FEATURE_1_AND must have pr_datasz = 4.
+TEST(note_gnu_property, pt_gnu_property_bad_pr_datasz) {
+#if defined(__aarch64__)
+  GnuPropertySectionBuilder prop;
+  ElfW(Word) pr_data[] = {GNU_PROPERTY_AARCH64_FEATURE_1_BTI, 0, 0};
+  ASSERT_TRUE(prop.push(GNU_PROPERTY_AARCH64_FEATURE_1_AND, 12, &pr_data));
+  PHDR_WITH_NOTE_GNU_PROPERTY(prop.data());
+  test_bti_not_supported(note);
+  ASSERT_ERROR_MSG_EQ(
+      ".note.gnu.property: property descriptor size is invalid. Expected 4 bytes for "
+      "GNU_PROPERTY_AARCH64_FEATURE_1_AND, got 12.");
+#else
+  GTEST_SKIP() << "BTI is not supported on this architecture.";
+#endif
+}
+
+// Tests a .note.gnu.property section with only GNU_PROPERTY_AARCH64_FEATURE_1_BTI property array.
+TEST(note_gnu_property, pt_gnu_property_ok_1) {
+#if defined(__aarch64__)
+  GnuPropertySectionBuilder prop;
+  ElfW(Word) pr_data[] = {GNU_PROPERTY_AARCH64_FEATURE_1_BTI};
+  ASSERT_TRUE(prop.push(GNU_PROPERTY_AARCH64_FEATURE_1_AND, sizeof(pr_data), &pr_data));
+  PHDR_WITH_NOTE_GNU_PROPERTY(prop.data());
+  ASSERT_NO_ERROR_MSG();
+  test_bti_supported(note);
+#else
+  GTEST_SKIP() << "BTI is not supported on this architecture.";
+#endif
+}
+
+// Tests a .note.gnu.property section with only GNU_PROPERTY_AARCH64_FEATURE_1_BTI property array.
+TEST(note_gnu_property, pt_gnu_property_ok_2) {
+#if defined(__aarch64__)
+  GnuPropertySectionBuilder prop;
+  ElfW(Word) pr_data[] = {static_cast<ElfW(Word)>(~GNU_PROPERTY_AARCH64_FEATURE_1_BTI)};
+  ASSERT_TRUE(prop.push(GNU_PROPERTY_AARCH64_FEATURE_1_AND, sizeof(pr_data), &pr_data));
+  PHDR_WITH_NOTE_GNU_PROPERTY(prop.data());
+  ASSERT_NO_ERROR_MSG();
+  test_bti_not_supported(note);
+#else
+  GTEST_SKIP() << "BTI is not supported on this architecture.";
+#endif
+}
+
+// Tests a .note.gnu.property section with more property arrays.
+TEST(note_gnu_property, pt_gnu_property_ok_3) {
+#if defined(__aarch64__)
+  GnuPropertySectionBuilder prop;
+
+  ElfW(Word) pr_data_0[8] = {0xCD};
+  ASSERT_TRUE(prop.push(1, 4, &pr_data_0));
+  ASSERT_TRUE(prop.push(2, 3, &pr_data_0));
+  ASSERT_TRUE(prop.push(3, 8, &pr_data_0));
+
+  ElfW(Word) pr_data[] = {GNU_PROPERTY_AARCH64_FEATURE_1_BTI};
+  ASSERT_TRUE(prop.push(GNU_PROPERTY_AARCH64_FEATURE_1_AND, sizeof(pr_data), &pr_data));
+
+  ASSERT_TRUE(prop.push(4, 1, &pr_data_0));
+
+  PHDR_WITH_NOTE_GNU_PROPERTY(prop.data());
+  ASSERT_NO_ERROR_MSG();
+  test_bti_supported(note);
+#else
+  GTEST_SKIP() << "BTI is not supported on this architecture.";
+#endif
+}
+
+// Tests a .note.gnu.property but with bad property descriptor size.
+TEST(note_gnu_property, pt_gnu_property_bad_n_descsz) {
+#if defined(__aarch64__)
+  GnuPropertySectionBuilder prop;
+  ElfW(Word) pr_data[] = {GNU_PROPERTY_AARCH64_FEATURE_1_BTI};
+  ASSERT_TRUE(prop.push(GNU_PROPERTY_AARCH64_FEATURE_1_AND, sizeof(pr_data), &pr_data));
+
+  ElfW(Word) n_descsz;
+  if (sizeof(ElfW(Addr)) == 4) {
+    n_descsz = 11;
+  } else {
+    n_descsz = 15;
+  }
+
+  prop.corrupt_n_descsz(n_descsz);
+
+  PHDR_WITH_NOTE_GNU_PROPERTY(prop.data());
+  if (sizeof(ElfW(Addr)) == 4) {
+    ASSERT_ERROR_MSG_EQ(
+        ".note.gnu.property: property descriptor size is invalid. Expected at least 12 bytes, got "
+        "11.");
+  } else {
+    ASSERT_ERROR_MSG_EQ(
+        ".note.gnu.property: property descriptor size is invalid. Expected at least 16 bytes, got "
+        "15.");
+  }
+  test_bti_not_supported(note);
+#else
+  GTEST_SKIP() << "BTI is not supported on this architecture.";
+#endif
+}
+
+// Tests if platform support is missing.
+TEST(note_gnu_property, no_platform_support) {
+#if defined(__aarch64__)
+  auto bti_supported_orig = g_platform_properties.bti_supported;
+  g_platform_properties.bti_supported = false;
+
+  GnuPropertySectionBuilder prop;
+  ElfW(Word) pr_data[] = {GNU_PROPERTY_AARCH64_FEATURE_1_BTI};
+  ASSERT_TRUE(prop.push(GNU_PROPERTY_AARCH64_FEATURE_1_AND, sizeof(pr_data), &pr_data));
+  PHDR_WITH_NOTE_GNU_PROPERTY(prop.data());
+  ASSERT_NO_ERROR_MSG();
+  test_bti_not_supported(note);
+
+  g_platform_properties.bti_supported = bti_supported_orig;
+#else
+  GTEST_SKIP() << "BTI is not supported on this architecture.";
+#endif
+}
diff --git a/linker/linker_phdr.cpp b/linker/linker_phdr.cpp
index 1e89094..9b1b99f 100644
--- a/linker/linker_phdr.cpp
+++ b/linker/linker_phdr.cpp
@@ -169,8 +169,16 @@
   if (did_load_) {
     return true;
   }
-  if (ReserveAddressSpace(address_space) && LoadSegments() && FindPhdr()) {
+  if (ReserveAddressSpace(address_space) && LoadSegments() && FindPhdr() &&
+      FindGnuPropertySection()) {
     did_load_ = true;
+#if defined(__aarch64__)
+    // For Armv8.5-A loaded executable segments may require PROT_BTI.
+    if (note_gnu_property_.IsBTICompatible()) {
+      did_load_ = (phdr_table_protect_segments(phdr_table_, phdr_num_, load_bias_,
+                                               &note_gnu_property_) == 0);
+    }
+#endif
   }
 
   return did_load_;
@@ -748,15 +756,21 @@
     ElfW(Addr) seg_page_start = PAGE_START(phdr->p_vaddr) + load_bias;
     ElfW(Addr) seg_page_end   = PAGE_END(phdr->p_vaddr + phdr->p_memsz) + load_bias;
 
-    int prot = PFLAGS_TO_PROT(phdr->p_flags);
-    if ((extra_prot_flags & PROT_WRITE) != 0) {
+    int prot = PFLAGS_TO_PROT(phdr->p_flags) | extra_prot_flags;
+    if ((prot & PROT_WRITE) != 0) {
       // make sure we're never simultaneously writable / executable
       prot &= ~PROT_EXEC;
     }
+#if defined(__aarch64__)
+    if ((prot & PROT_EXEC) == 0) {
+      // Though it is not specified don't add PROT_BTI if segment is not
+      // executable.
+      prot &= ~PROT_BTI;
+    }
+#endif
 
-    int ret = mprotect(reinterpret_cast<void*>(seg_page_start),
-                       seg_page_end - seg_page_start,
-                       prot | extra_prot_flags);
+    int ret =
+        mprotect(reinterpret_cast<void*>(seg_page_start), seg_page_end - seg_page_start, prot);
     if (ret < 0) {
       return -1;
     }
@@ -768,16 +782,26 @@
  * You should only call this after phdr_table_unprotect_segments and
  * applying all relocations.
  *
+ * AArch64: also called from linker_main and ElfReader::Load to apply
+ *     PROT_BTI for loaded main so and other so-s.
+ *
  * Input:
  *   phdr_table  -> program header table
  *   phdr_count  -> number of entries in tables
  *   load_bias   -> load bias
+ *   prop        -> GnuPropertySection or nullptr
  * Return:
  *   0 on error, -1 on failure (error code in errno).
  */
-int phdr_table_protect_segments(const ElfW(Phdr)* phdr_table,
-                                size_t phdr_count, ElfW(Addr) load_bias) {
-  return _phdr_table_set_load_prot(phdr_table, phdr_count, load_bias, 0);
+int phdr_table_protect_segments(const ElfW(Phdr)* phdr_table, size_t phdr_count,
+                                ElfW(Addr) load_bias, const GnuPropertySection* prop __unused) {
+  int prot = 0;
+#if defined(__aarch64__)
+  if ((prop != nullptr) && prop->IsBTICompatible()) {
+    prot |= PROT_BTI;
+  }
+#endif
+  return _phdr_table_set_load_prot(phdr_table, phdr_count, load_bias, prot);
 }
 
 /* Change the protection of all loaded segments in memory to writable.
@@ -1081,7 +1105,7 @@
  * Return:
  *   pointer to the program interpreter string.
  */
-const char* phdr_table_get_interpreter_name(const ElfW(Phdr) * phdr_table, size_t phdr_count,
+const char* phdr_table_get_interpreter_name(const ElfW(Phdr)* phdr_table, size_t phdr_count,
                                             ElfW(Addr) load_bias) {
   for (size_t i = 0; i<phdr_count; ++i) {
     const ElfW(Phdr)& phdr = phdr_table[i];
@@ -1124,6 +1148,15 @@
   return false;
 }
 
+// Tries to find .note.gnu.property section.
+// It is not considered an error if such section is missing.
+bool ElfReader::FindGnuPropertySection() {
+#if defined(__aarch64__)
+  note_gnu_property_ = GnuPropertySection(phdr_table_, phdr_num_, load_start(), name_.c_str());
+#endif
+  return true;
+}
+
 // Ensures that our program header is actually within a loadable
 // segment. This should help catch badly-formed ELF files that
 // would cause the linker to crash later when trying to access it.
diff --git a/linker/linker_phdr.h b/linker/linker_phdr.h
index 4cb48f5..548dc51 100644
--- a/linker/linker_phdr.h
+++ b/linker/linker_phdr.h
@@ -37,6 +37,7 @@
 
 #include "linker.h"
 #include "linker_mapped_file_fragment.h"
+#include "linker_note_gnu_property.h"
 
 class ElfReader {
  public:
@@ -67,6 +68,7 @@
   bool ReserveAddressSpace(address_space_params* address_space);
   bool LoadSegments();
   bool FindPhdr();
+  bool FindGnuPropertySection();
   bool CheckPhdr(ElfW(Addr));
   bool CheckFileRange(ElfW(Addr) offset, size_t size, size_t alignment);
 
@@ -110,13 +112,16 @@
 
   // Is map owned by the caller
   bool mapped_by_caller_;
+
+  // Only used by AArch64 at the moment.
+  GnuPropertySection note_gnu_property_ __unused;
 };
 
 size_t phdr_table_get_load_size(const ElfW(Phdr)* phdr_table, size_t phdr_count,
                                 ElfW(Addr)* min_vaddr = nullptr, ElfW(Addr)* max_vaddr = nullptr);
 
-int phdr_table_protect_segments(const ElfW(Phdr)* phdr_table,
-                                size_t phdr_count, ElfW(Addr) load_bias);
+int phdr_table_protect_segments(const ElfW(Phdr)* phdr_table, size_t phdr_count,
+                                ElfW(Addr) load_bias, const GnuPropertySection* prop = nullptr);
 
 int phdr_table_unprotect_segments(const ElfW(Phdr)* phdr_table, size_t phdr_count,
                                   ElfW(Addr) load_bias);
@@ -139,5 +144,5 @@
                                     ElfW(Addr) load_bias, ElfW(Dyn)** dynamic,
                                     ElfW(Word)* dynamic_flags);
 
-const char* phdr_table_get_interpreter_name(const ElfW(Phdr) * phdr_table, size_t phdr_count,
+const char* phdr_table_get_interpreter_name(const ElfW(Phdr)* phdr_table, size_t phdr_count,
                                             ElfW(Addr) load_bias);