libunwindstack: support for Armv8.3-A Pointer Authentication

This patch adds support for handling return addresses signed with
pointer authentication. It simply strips the authentication code
without verifying its correctness, and thus works with both A and B
keys and through key-change boundaries.

Additons:
  * DW_CFA_AARCH64_negate_ra_state: new CFA operation.
  * RA_SIGN_STATE: new pseudo register.
  * Pass the arch to DwarfCfa so that the new op is only executed
    on aarch64.

The stripping uses the xpaclri instruction. This is a hint space
instruction which is compatible with pre Armv8.3-A devices. For cases
where it cannot be used, a mask can be set instead.

Test: libunwindstack_test
      Without this patch all UnwindTest.* testcases should fail if
      compiled with Pointer Authentication.

The tests should be executed with both -mbranch-protection=pac-ret and
pac-ret+leaf flags so that either some or all functions have pointer
authentication instructions.

Change-Id: Id7c3f1d0e2fc7fccb19bd1430826264405a9df7c
diff --git a/libunwindstack/tests/DwarfCfaTest.cpp b/libunwindstack/tests/DwarfCfaTest.cpp
index 9c6ab05..ea7e708 100644
--- a/libunwindstack/tests/DwarfCfaTest.cpp
+++ b/libunwindstack/tests/DwarfCfaTest.cpp
@@ -25,7 +25,9 @@
 #include <unwindstack/DwarfLocation.h>
 #include <unwindstack/DwarfMemory.h>
 #include <unwindstack/DwarfStructs.h>
+#include <unwindstack/Elf.h>
 #include <unwindstack/Log.h>
+#include <unwindstack/MachineArm64.h>
 
 #include "DwarfCfa.h"
 
@@ -55,7 +57,7 @@
     fde_.pc_start = 0x2000;
     fde_.cie = &cie_;
 
-    cfa_.reset(new DwarfCfa<TypeParam>(dmem_.get(), &fde_));
+    cfa_.reset(new DwarfCfa<TypeParam>(dmem_.get(), &fde_, ARCH_UNKNOWN));
   }
 
   MemoryFake memory_;
@@ -70,8 +72,8 @@
 
 TYPED_TEST_P(DwarfCfaTest, cfa_illegal) {
   for (uint8_t i = 0x17; i < 0x3f; i++) {
-    if (i == 0x2e || i == 0x2f) {
-      // Skip gnu extension ops.
+    if (i == 0x2d || i == 0x2e || i == 0x2f) {
+      // Skip gnu extension ops and aarch64 specialized op.
       continue;
     }
     this->memory_.SetMemory(0x2000, std::vector<uint8_t>{i});
@@ -952,6 +954,57 @@
   ASSERT_EQ("", GetFakeLogBuf());
 }
 
+TYPED_TEST_P(DwarfCfaTest, cfa_aarch64_negate_ra_state) {
+  this->memory_.SetMemory(0x2000, std::vector<uint8_t>{0x2d});
+  dwarf_loc_regs_t loc_regs;
+
+  ASSERT_FALSE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2001, &loc_regs));
+  ASSERT_EQ(DWARF_ERROR_ILLEGAL_VALUE, this->cfa_->LastErrorCode());
+  ASSERT_EQ(0x2001U, this->dmem_->cur_offset());
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  ResetLogs();
+  this->cfa_.reset(new DwarfCfa<TypeParam>(this->dmem_.get(), &this->fde_, ARCH_ARM64));
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2001, &loc_regs));
+  ASSERT_EQ(0x2001U, this->dmem_->cur_offset());
+
+  auto location = loc_regs.find(Arm64Reg::ARM64_PREG_RA_SIGN_STATE);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_PSEUDO_REGISTER, location->second.type);
+  ASSERT_EQ(1U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  // Verify that the value is set to 0 after another evaluation.
+  ResetLogs();
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2001, &loc_regs));
+  ASSERT_EQ(0x2001U, this->dmem_->cur_offset());
+
+  location = loc_regs.find(Arm64Reg::ARM64_PREG_RA_SIGN_STATE);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_PSEUDO_REGISTER, location->second.type);
+  ASSERT_EQ(0U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+
+  // Verify that the value is set to 1 again after a third op.
+  ResetLogs();
+  ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2001, &loc_regs));
+  ASSERT_EQ(0x2001U, this->dmem_->cur_offset());
+
+  location = loc_regs.find(Arm64Reg::ARM64_PREG_RA_SIGN_STATE);
+  ASSERT_NE(loc_regs.end(), location);
+  ASSERT_EQ(DWARF_LOCATION_PSEUDO_REGISTER, location->second.type);
+  ASSERT_EQ(1U, location->second.values[0]);
+
+  ASSERT_EQ("", GetFakeLogPrint());
+  ASSERT_EQ("", GetFakeLogBuf());
+}
+
 REGISTER_TYPED_TEST_SUITE_P(DwarfCfaTest, cfa_illegal, cfa_nop, cfa_offset, cfa_offset_extended,
                             cfa_offset_extended_sf, cfa_restore, cfa_restore_extended, cfa_set_loc,
                             cfa_advance_loc1, cfa_advance_loc2, cfa_advance_loc4, cfa_undefined,
@@ -960,7 +1013,7 @@
                             cfa_def_cfa_offset_sf, cfa_def_cfa_expression, cfa_expression,
                             cfa_val_offset, cfa_val_offset_sf, cfa_val_expression,
                             cfa_gnu_args_size, cfa_gnu_negative_offset_extended,
-                            cfa_register_override);
+                            cfa_register_override, cfa_aarch64_negate_ra_state);
 
 typedef ::testing::Types<uint32_t, uint64_t> DwarfCfaTestTypes;
 INSTANTIATE_TYPED_TEST_SUITE_P(Libunwindstack, DwarfCfaTest, DwarfCfaTestTypes);