From: Luis Machado Date: Mon, 1 Nov 2021 20:14:26 +0000 (-0300) Subject: Enable ARMv8.1-m PACBTI support X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=a01567f4f7fbbc6ce1492b359c762fcf39e71b4b;p=binutils-gdb.git Enable ARMv8.1-m PACBTI support This set of changes enable support for the ARMv8.1-m PACBTI extensions [1]. The goal of the PACBTI extensions is similar in scope to that of a-profile PAC/BTI (aarch64 only), but the underlying implementation is different. One important difference is that the pointer authentication code is stored in a separate register, thus we don't need to mask/unmask the return address from a function in order to produce a correct backtrace. The patch introduces the following modifications: - Extend the prologue analyser for 32-bit ARM to handle some instructions from ARMv8.1-m PACBTI: pac, aut, pacg, autg and bti. Also keep track of return address signing/authentication instructions. - Adds code to identify object file attributes that indicate the presence of ARMv8.1-m PACBTI (Tag_PAC_extension, Tag_BTI_extension, Tag_PACRET_use and Tag_BTI_use). - Adds support for DWARF pseudo-register RA_AUTH_CODE, as described in the aadwarf32 [2]. - Extends the dwarf unwinder to track the value of RA_AUTH_CODE. - Decorates backtraces with the "[PAC]" identifier when a frame has signed the return address. - Makes GDB aware of a new XML feature "org.gnu.gdb.arm.m-profile-pacbti". This feature is not included as an XML file on GDB's side because it is only supported for bare metal targets. - Additional documentation. [1] https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/armv8-1-m-pointer-authentication-and-branch-target-identification-extension [2] https://github.com/ARM-software/abi-aa/blob/main/aadwarf32/aadwarf32.rst --- diff --git a/gdb/arch/arm.h b/gdb/arch/arm.h index f75470e7572..755391fe6fe 100644 --- a/gdb/arch/arm.h +++ b/gdb/arch/arm.h @@ -21,6 +21,19 @@ #include "gdbsupport/tdesc.h" +/* Prologue helper macros for ARMv8.1-m PACBTI. */ +#define IS_PAC(instruction) (instruction == 0xf3af801d) +#define IS_PACBTI(instruction) (instruction == 0xf3af800d) +#define IS_BTI(instruction) (instruction == 0xf3af800f) +#define IS_PACG(instruction) ((instruction & 0xfff0f0f0) == 0xfb60f000) +#define IS_AUT(instruction) (instruction == 0xf3af802d) +#define IS_AUTG(instruction) ((instruction & 0xfff00ff0) == 0xfb500f00) + +/* DWARF register numbers according to the AADWARF32 document. */ +enum arm_dwarf_regnum { + ARM_DWARF_RA_AUTH_CODE = 143 +}; + /* Register numbers of various important registers. */ enum gdb_regnum { diff --git a/gdb/arm-tdep.c b/gdb/arm-tdep.c index 4d0f3492410..3e91dd707b0 100644 --- a/gdb/arm-tdep.c +++ b/gdb/arm-tdep.c @@ -38,6 +38,7 @@ #include "frame-base.h" #include "trad-frame.h" #include "objfiles.h" +#include "dwarf2.h" #include "dwarf2/frame.h" #include "gdbtypes.h" #include "prologue-value.h" @@ -286,6 +287,9 @@ struct arm_prologue_cache /* The register used to hold the frame pointer for this frame. */ int framereg; + /* True if the return address is signed, false otherwise. */ + gdb::optional ra_signed_state; + /* Saved register offsets. */ trad_frame_saved_reg *saved_regs; }; @@ -713,6 +717,7 @@ thumb_analyze_prologue (struct gdbarch *gdbarch, while (start < limit) { unsigned short insn; + gdb::optional ra_signed_state; insn = read_code_unsigned_integer (start, 2, byte_order_for_code); @@ -847,6 +852,7 @@ thumb_analyze_prologue (struct gdbarch *gdbarch, inst2 = read_code_unsigned_integer (start + 2, 2, byte_order_for_code); + uint32_t whole_insn = (insn << 16) | inst2; if ((insn & 0xf800) == 0xf000 && (inst2 & 0xe800) == 0xe800) { @@ -1100,7 +1106,37 @@ thumb_analyze_prologue (struct gdbarch *gdbarch, constant = read_memory_unsigned_integer (loc + 4, 4, byte_order); regs[bits (inst2, 8, 11)] = pv_constant (constant); } - + /* Start of ARMv8.1-m PACBTI extension instructions. */ + else if (IS_PAC (whole_insn)) + { + /* LR and SP are input registers. PAC is in R12. LR is + signed from this point onwards. NOP space. */ + ra_signed_state = true; + } + else if (IS_PACBTI (whole_insn)) + { + /* LR and SP are input registers. PAC is in R12 and PC is a + valid BTI landing pad. LR is signed from this point onwards. + NOP space. */ + ra_signed_state = true; + } + else if (IS_BTI (whole_insn)) + { + /* Valid BTI landing pad. NOP space. */ + } + else if (IS_PACG (whole_insn)) + { + /* Sign Rn using Rm and store the PAC in Rd. Rd is signed from + this point onwards. */ + ra_signed_state = true; + } + else if (IS_AUT (whole_insn) || IS_AUTG (whole_insn)) + { + /* These instructions appear close to the epilogue, when signed + pointers are getting authenticated. */ + ra_signed_state = false; + } + /* End of ARMv8.1-m PACBTI extension instructions */ else if (thumb2_instruction_changes_pc (insn, inst2)) { /* Don't scan past anything that might change control flow. */ @@ -1113,6 +1149,21 @@ thumb_analyze_prologue (struct gdbarch *gdbarch, unrecognized_pc = start; } + arm_gdbarch_tdep *tdep + = (arm_gdbarch_tdep *) gdbarch_tdep (gdbarch); + + /* Make sure we are dealing with a target that supports ARMv8.1-m + PACBTI. */ + if (cache != nullptr && tdep->have_pacbti + && ra_signed_state.has_value ()) + { + arm_debug_printf ("Found pacbti instruction at %s", + paddress (gdbarch, start)); + arm_debug_printf ("RA is %s", + *ra_signed_state? "signed" : "not signed"); + cache->ra_signed_state = ra_signed_state; + } + start += 2; } else if (thumb_instruction_changes_pc (insn)) @@ -1988,6 +2039,13 @@ arm_prologue_prev_register (struct frame_info *this_frame, *this_cache = arm_make_prologue_cache (this_frame); cache = (struct arm_prologue_cache *) *this_cache; + arm_gdbarch_tdep *tdep = (arm_gdbarch_tdep *) gdbarch_tdep (gdbarch); + + /* If this frame has signed the return address, mark it as so. */ + if (tdep->have_pacbti && cache->ra_signed_state.has_value () + && *cache->ra_signed_state) + set_frame_previous_pc_masked (this_frame); + /* If we are asked to unwind the PC, then we need to return the LR instead. The prologue may save PC, but it will point into this frame's prologue, not the next frame's resume location. Also @@ -3216,6 +3274,7 @@ arm_dwarf2_prev_register (struct frame_info *this_frame, void **this_cache, int regnum) { struct gdbarch * gdbarch = get_frame_arch (this_frame); + arm_gdbarch_tdep *tdep = (arm_gdbarch_tdep *) gdbarch_tdep (gdbarch); CORE_ADDR lr, cpsr; ULONGEST t_bit = arm_psr_thumb_bit (gdbarch); @@ -3226,6 +3285,18 @@ arm_dwarf2_prev_register (struct frame_info *this_frame, void **this_cache, describes saves of LR. However, that version may have an extra bit set to indicate Thumb state. The bit is not part of the PC. */ + + /* Record in the frame whether the return address was signed. */ + if (tdep->have_pacbti) + { + CORE_ADDR ra_auth_code + = frame_unwind_register_unsigned (this_frame, + tdep->pacbti_pseudo_base); + + if (ra_auth_code != 0) + set_frame_previous_pc_masked (this_frame); + } + lr = frame_unwind_register_unsigned (this_frame, ARM_LR_REGNUM); return frame_unwind_got_constant (this_frame, regnum, arm_addr_bits_remove (gdbarch, lr)); @@ -3246,24 +3317,6 @@ arm_dwarf2_prev_register (struct frame_info *this_frame, void **this_cache, } } -static void -arm_dwarf2_frame_init_reg (struct gdbarch *gdbarch, int regnum, - struct dwarf2_frame_state_reg *reg, - struct frame_info *this_frame) -{ - switch (regnum) - { - case ARM_PC_REGNUM: - case ARM_PS_REGNUM: - reg->how = DWARF2_FRAME_REG_FN; - reg->loc.fn = arm_dwarf2_prev_register; - break; - case ARM_SP_REGNUM: - reg->how = DWARF2_FRAME_REG_CFA; - break; - } -} - /* Implement the stack_frame_destroyed_p gdbarch method. */ static int @@ -4193,6 +4246,25 @@ is_mve_pseudo (struct gdbarch *gdbarch, int regnum) return false; } +/* Return true if REGNUM is a PACBTI pseudo register (ra_auth_code). Return + false otherwise. + + REGNUM is the raw register number and not a pseudo-relative register + number. */ + +static bool +is_pacbti_pseudo (struct gdbarch *gdbarch, int regnum) +{ + arm_gdbarch_tdep *tdep = (arm_gdbarch_tdep *) gdbarch_tdep (gdbarch); + + if (tdep->have_pacbti + && regnum >= tdep->pacbti_pseudo_base + && regnum < tdep->pacbti_pseudo_base + tdep->pacbti_pseudo_count) + return true; + + return false; +} + /* Return the GDB type object for the "standard" data type of data in register N. */ @@ -4210,6 +4282,9 @@ arm_register_type (struct gdbarch *gdbarch, int regnum) if (is_mve_pseudo (gdbarch, regnum)) return builtin_type (gdbarch)->builtin_int16; + if (is_pacbti_pseudo (gdbarch, regnum)) + return builtin_type (gdbarch)->builtin_uint32; + /* If the target description has register information, we are only in this function so that we can override the types of double-precision registers for NEON. */ @@ -4272,6 +4347,17 @@ arm_dwarf_reg_to_regnum (struct gdbarch *gdbarch, int reg) if (reg >= 112 && reg <= 127) return ARM_WR0_REGNUM + reg - 112; + /* PACBTI register containing the Pointer Authentication Code. */ + if (reg == ARM_DWARF_RA_AUTH_CODE) + { + arm_gdbarch_tdep *tdep = (arm_gdbarch_tdep *) gdbarch_tdep (gdbarch); + + if (tdep->have_pacbti) + return tdep->pacbti_pseudo_base; + + return -1; + } + if (reg >= 192 && reg <= 199) return ARM_WC0_REGNUM + reg - 192; @@ -4337,6 +4423,35 @@ arm_register_sim_regno (struct gdbarch *gdbarch, int regnum) internal_error (__FILE__, __LINE__, _("Bad REGNUM %d"), regnum); } +static const unsigned char op_lit0 = DW_OP_lit0; + +static void +arm_dwarf2_frame_init_reg (struct gdbarch *gdbarch, int regnum, + struct dwarf2_frame_state_reg *reg, + struct frame_info *this_frame) +{ + if (is_pacbti_pseudo (gdbarch, regnum)) + { + /* Initialize RA_AUTH_CODE to zero. */ + reg->how = DWARF2_FRAME_REG_SAVED_VAL_EXP; + reg->loc.exp.start = &op_lit0; + reg->loc.exp.len = 1; + return; + } + + switch (regnum) + { + case ARM_PC_REGNUM: + case ARM_PS_REGNUM: + reg->how = DWARF2_FRAME_REG_FN; + reg->loc.fn = arm_dwarf2_prev_register; + break; + case ARM_SP_REGNUM: + reg->how = DWARF2_FRAME_REG_CFA; + break; + } +} + /* Given BUF, which is OLD_LEN bytes ending at ENDADDR, expand the buffer to be NEW_LEN bytes ending at ENDADDR. Return NULL if an error occurs. BUF is freed. */ @@ -8683,6 +8798,10 @@ arm_register_name (struct gdbarch *gdbarch, int i) if (is_mve_pseudo (gdbarch, i)) return "p0"; + /* RA_AUTH_CODE is used for unwinding only. Do not assign it a name. */ + if (is_pacbti_pseudo (gdbarch, i)) + return ""; + if (i >= ARRAY_SIZE (arm_register_names)) /* These registers are only supported on targets which supply an XML description. */ @@ -9073,6 +9192,17 @@ arm_gnu_triplet_regexp (struct gdbarch *gdbarch) return gdbarch_bfd_arch_info (gdbarch)->arch_name; } +/* Implement the "get_pc_address_flags" gdbarch method. */ + +static std::string +arm_get_pc_address_flags (frame_info *frame, CORE_ADDR pc) +{ + if (get_frame_pc_masked (frame)) + return "PAC"; + + return ""; +} + /* Initialize the current architecture based on INFO. If possible, re-use an architecture from ARCHES, which is a list of architectures already created during this debugging session. @@ -9098,6 +9228,7 @@ arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) const struct target_desc *tdesc = info.target_desc; bool have_vfp = false; bool have_mve = false; + bool have_pacbti = false; int mve_vpr_regnum = -1; int register_count = ARM_NUM_REGS; @@ -9220,6 +9351,31 @@ arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) || attr_arch == TAG_CPU_ARCH_V8_1M_MAIN || attr_profile == 'M')) is_m = true; + + /* Look for attributes that indicate support for ARMv8.1-m + PACBTI. */ + if (!tdesc_has_registers (tdesc) && is_m) + { + int attr_pac_extension + = bfd_elf_get_obj_attr_int (info.abfd, OBJ_ATTR_PROC, + Tag_PAC_extension); + + int attr_bti_extension + = bfd_elf_get_obj_attr_int (info.abfd, OBJ_ATTR_PROC, + Tag_BTI_extension); + + int attr_pacret_use + = bfd_elf_get_obj_attr_int (info.abfd, OBJ_ATTR_PROC, + Tag_PACRET_use); + + int attr_bti_use + = bfd_elf_get_obj_attr_int (info.abfd, OBJ_ATTR_PROC, + Tag_BTI_use); + + if (attr_pac_extension != 0 || attr_bti_extension != 0 + || attr_pacret_use != 0 || attr_bti_use != 0) + have_pacbti = true; + } #endif } @@ -9446,6 +9602,22 @@ arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) if (have_vfp) have_q_pseudos = true; } + + /* Do we have the ARMv8.1-m PACBTI feature? */ + feature = tdesc_find_feature (tdesc, + "org.gnu.gdb.arm.m-profile-pacbti"); + if (feature != nullptr) + { + /* By advertising this feature, the target acknowledges the + presence of the ARMv8.1-m PACBTI extensions. + + We don't care for any particular registers in this group, so + the target is free to include whatever it deems appropriate. + + The expectation is for this feature to include the PAC + keys. */ + have_pacbti = true; + } } } @@ -9472,6 +9644,11 @@ arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) if (is_m != tdep->is_m) continue; + /* Also check for ARMv8.1-m PACBTI support, since it might come from + the binary. */ + if (have_pacbti != tdep->have_pacbti) + continue; + /* Found a match. */ break; } @@ -9504,6 +9681,9 @@ arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) tdep->mve_vpr_regnum = mve_vpr_regnum; } + /* Adjust the PACBTI feature settings. */ + tdep->have_pacbti = have_pacbti; + arm_register_g_packet_guesses (gdbarch); /* Breakpoints. */ @@ -9672,6 +9852,11 @@ arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) set_gdbarch_long_double_format (gdbarch, floatformats_ieee_double); } + /* Hook used to decorate frames with signed return addresses, only available + for ARMv8.1-m PACBTI. */ + if (is_m && have_pacbti) + set_gdbarch_get_pc_address_flags (gdbarch, arm_get_pc_address_flags); + if (tdesc_data != nullptr) { set_tdesc_pseudo_register_name (gdbarch, arm_register_name); @@ -9715,8 +9900,16 @@ arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) num_pseudos += tdep->mve_pseudo_count; } + /* Do we have any ARMv8.1-m PACBTI pseudo registers. */ + if (have_pacbti) + { + tdep->pacbti_pseudo_base = register_count + num_pseudos; + tdep->pacbti_pseudo_count = 1; + num_pseudos += tdep->pacbti_pseudo_count; + } + /* Set some pseudo register hooks, if we have pseudo registers. */ - if (tdep->have_s_pseudos || have_mve) + if (tdep->have_s_pseudos || have_mve || have_pacbti) { set_gdbarch_num_pseudo_regs (gdbarch, num_pseudos); set_gdbarch_pseudo_register_read (gdbarch, arm_pseudo_read); @@ -9778,6 +9971,14 @@ arm_dump_tdep (struct gdbarch *gdbarch, struct ui_file *file) tdep->mve_pseudo_count); gdb_printf (file, _("arm_dump_tdep: Lowest pc = 0x%lx\n"), (unsigned long) tdep->lowest_pc); + gdb_printf (file, _("arm_dump_tdep: have_pacbti = %s\n"), + tdep->have_pacbti? "yes" : "no"); + gdb_printf (file, _("arm_dump_tdep: pacbti_pseudo_base = %i\n"), + tdep->pacbti_pseudo_base); + gdb_printf (file, _("arm_dump_tdep: pacbti_pseudo_count = %i\n"), + tdep->pacbti_pseudo_count); + gdb_printf (file, _("arm_dump_tdep: is_m = %s\n"), + tdep->is_m? "yes" : "no"); } #if GDB_SELF_TEST diff --git a/gdb/arm-tdep.h b/gdb/arm-tdep.h index 8a9f618539f..ae32fffcabf 100644 --- a/gdb/arm-tdep.h +++ b/gdb/arm-tdep.h @@ -119,6 +119,12 @@ struct arm_gdbarch_tdep : gdbarch_tdep int mve_pseudo_base = 0; /* Number of the first MVE pseudo register. */ int mve_pseudo_count = 0; /* Total number of MVE pseudo registers. */ + bool have_pacbti = false; /* True if we have the ARMv8.1-m PACBTI + extensions. */ + int pacbti_pseudo_base = 0; /* Number of the first PACBTI pseudo + register. */ + int pacbti_pseudo_count = 0; /* Total number of PACBTI pseudo registers. */ + bool is_m = false; /* Does the target follow the "M" profile. */ CORE_ADDR lowest_pc = 0; /* Lowest address at which instructions will appear. */ diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index e50618fe9ab..e4685cd995b 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -25409,6 +25409,7 @@ target process. @subsubsection AArch64 Pointer Authentication. @cindex AArch64 Pointer Authentication. +@anchor{AArch64 PAC} When @value{GDBN} is debugging the AArch64 architecture, and the program is using the v8.3-A feature Pointer Authentication (PAC), then whenever the link @@ -46604,6 +46605,12 @@ quad-precision registers from pairs of double-precision registers. If this feature is present, @samp{org.gnu.gdb.arm.vfp} must also be present and include 32 double-precision registers. +The @samp{org.gnu.gdb.arm.m-profile-pacbti} feature is optional, and +acknowledges support for the ARMv8.1-m PACBTI extensions. @value{GDBN} +will track return address signing states and will decorate backtraces using +the [PAC] marker, similar to AArch64's PAC extension. +@xref{AArch64 PAC}. + @node i386 Features @subsection i386 Features @cindex target descriptions, i386 features