SFrame V3 introduces flexible FDEs in addition to the regular FDEs.
The key difference is that flexible FDEs encode the CFA, RA, and FP
tracking information using two FRE data words, a control word and an
offset, or a single padding data word of zero (e.g. to represent FP
without RA tracking information).

The control word contains the following information:
- reg_p: Whether to use the register contents (reg_p=1) specified
  by regnum or the CFA (reg_p=0) as base.
- deref_p: Whether to dereference.
- regnum: A DWARF register number.

The offset is added to the base (i.e. CFA or register contents).  Then
the resulting address may optionally be dereferenced.

This enables the following flexible CFA and FP/RA recovery rules:
- CFA = register + offset       // reg_p=1, deref_p=0
- CFA = *(register + offset)    // reg_p=1, deref_p=1
- FP/RA = *(CFA + offset)       // reg_p=0, deref_p=0
- FP/RA = register + offset     // reg_p=1, deref_p=0
- FP/RA = *(register + offset)  // reg_p=1, deref_p=1

Note that for the CFA a rule with reg_p=0 is invalid, as the value of
the CFA cannot be described using itself as base.  For FP/RA a rule with
reg_p=0 and deref_p=0 and regnum=0 is invalid, as it that is equal to
the padding data word of zero.

Reviewed-by: Indu Bhagat <[email protected]>
Signed-off-by: Jens Remus <[email protected]>
---

Notes (jremus):
    Changes in v15:
    - __read_flex_fde_fre_datawords(): Add comment on FRE dataword RA/FP
      location info decoding logic. (Sashiko AI)
    - Fix outermost frame (FRE without datawords) handling to not cause
      sframe_init_cfa_rule_data() and ultimately sframe_find() to fail
      with -EINVAL. (Sashiko AI)
    - sframe_init_[cfa_]rule_data(): Reject FRE control word with
      reserved_p=1. (Sashiko AI)
    - __find_fre(): Return RC of sframe_init_[cfa_]rule_data() if bad RC.
    - Normalize error code usage (.sframe is removed for all but ENOENT):
      ENOENT: No sframe or no FDE for IP found
              (FDE found but no FRE is EINVAL)
      EFAULT: Bad address
      EINVAL: Invalid input or sframe
    
    Changes in v14:
    - Rename __read_regular_fre_datawords() to
      __read_default_fre_datawords() to align to SFrame V3 specification
      (default FRE).
    - Rename SFRAME_FDE_TYPE_FLEXIBLE to SFRAME_FDE_TYPE_FLEX to match
      SFrame V3 specification and adjust to rename of SFRAME_FDE_TYPE_*.
    - Rename SFRAME_V3_FLEX_FDE_CTLWORD_*() to
      SFRAME_V3_FLEX_FDE_CTRLWORD_*() to match SFrame V3 reference
      implementation.
    - Add arch/*/include/asm/unwind_user_sframe.h to MAINTAINERS.

 MAINTAINERS            |   1 +
 kernel/unwind/sframe.c | 282 +++++++++++++++++++++++++++++++++--------
 kernel/unwind/sframe.h |   6 +
 3 files changed, 237 insertions(+), 52 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index a9b42b67a88d..25f0b933511c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27875,6 +27875,7 @@ M:      Josh Poimboeuf <[email protected]>
 M:     Steven Rostedt <[email protected]>
 S:     Maintained
 F:     arch/*/include/asm/unwind_user.h
+F:     arch/*/include/asm/unwind_user_sframe.h
 F:     include/asm-generic/unwind_user.h
 F:     include/linux/sframe.h
 F:     include/linux/unwind*.h
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index 6187379750db..48709f0bafc7 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -12,6 +12,7 @@
 #include <linux/mm.h>
 #include <linux/string_helpers.h>
 #include <linux/sframe.h>
+#include <asm/unwind_user_sframe.h>
 #include <linux/unwind_user_types.h>
 
 #include "sframe.h"
@@ -31,8 +32,11 @@ struct sframe_fde_internal {
 struct sframe_fre_internal {
        unsigned int    size;
        u32             ip_off;
+       u32             cfa_ctl;
        s32             cfa_off;
+       u32             ra_ctl;
        s32             ra_off;
+       u32             fp_ctl;
        s32             fp_off;
        u8              info;
 };
@@ -200,16 +204,158 @@ static __always_inline int __find_fde(struct 
sframe_section *sec,
                 s32 :  UNSAFE_GET_USER_SIGNED_INC(to, from, size, label),      
\
                 s64 :  UNSAFE_GET_USER_SIGNED_INC(to, from, size, label))
 
+static __always_inline int
+__read_default_fre_datawords(struct sframe_section *sec,
+                            struct sframe_fde_internal *fde,
+                            unsigned long cur,
+                            unsigned char dataword_count,
+                            unsigned char dataword_size,
+                            struct sframe_fre_internal *fre)
+{
+       s32 cfa_off, ra_off, fp_off;
+       unsigned int cfa_regnum;
+
+       UNSAFE_GET_USER_INC(cfa_off, cur, dataword_size, Efault);
+       dataword_count--;
+
+       ra_off = sec->ra_off;
+       if (!ra_off && dataword_count) {
+               dataword_count--;
+               UNSAFE_GET_USER_INC(ra_off, cur, dataword_size, Efault);
+       }
+
+       fp_off = sec->fp_off;
+       if (!fp_off && dataword_count) {
+               dataword_count--;
+               UNSAFE_GET_USER_INC(fp_off, cur, dataword_size, Efault);
+       }
+
+       if (dataword_count)
+               return -EINVAL;
+
+       cfa_regnum =
+               (SFRAME_V3_FRE_CFA_BASE_REG_ID(fre->info) == 
SFRAME_BASE_REG_FP) ?
+                       SFRAME_REG_FP : SFRAME_REG_SP;
+
+       fre->cfa_ctl    = (cfa_regnum << 3) | 1; /* regnum, deref_p=0, reg_p=1 
*/
+       fre->cfa_off    = cfa_off;
+       fre->ra_ctl     = ra_off ? 2 : 0; /* regnum=0, deref_p=(ra_off != 0), 
reg_p=0 */
+       fre->ra_off     = ra_off;
+       fre->fp_ctl     = fp_off ? 2 : 0; /* regnum=0, deref_p=(fp_off != 0), 
reg_p=0 */
+       fre->fp_off     = fp_off;
+
+       return 0;
+
+Efault:
+       return -EFAULT;
+}
+
+static __always_inline int
+__read_flex_fde_fre_datawords(struct sframe_section *sec,
+                             struct sframe_fde_internal *fde,
+                             unsigned long cur,
+                             unsigned char dataword_count,
+                             unsigned char dataword_size,
+                             struct sframe_fre_internal *fre)
+{
+       u32 cfa_ctl, ra_ctl, fp_ctl;
+       s32 cfa_off, ra_off, fp_off;
+
+       if (dataword_count < 2)
+               return -EINVAL;
+       UNSAFE_GET_USER_INC(cfa_ctl, cur, dataword_size, Efault);
+       UNSAFE_GET_USER_INC(cfa_off, cur, dataword_size, Efault);
+       dataword_count -= 2;
+
+       /*
+        * Each RA/FP location info consumes either two datawords
+        * (control word + offset) or one padding word substituting
+        * for that pair.  Padding is only valid as substitution if
+        * followed by further non-padding location info.  Therefore
+        * decoding only proceeds with at least two datawords.  Any
+        * leftover trailing datawords are invalid and rejected by
+        * the final check.
+        */
+
+       ra_off = sec->ra_off;
+       ra_ctl = ra_off ? 2 : 0; /* regnum=0, deref_p=(ra_off != 0), reg_p=0 */
+       if (dataword_count >= 2) {
+               UNSAFE_GET_USER_INC(ra_ctl, cur, dataword_size, Efault);
+               dataword_count--;
+               if (ra_ctl) {
+                       UNSAFE_GET_USER_INC(ra_off, cur, dataword_size, Efault);
+                       dataword_count--;
+               } else {
+                       /* Padding RA location info */
+                       ra_ctl = ra_off ? 2 : 0; /* re-deduce (see above) */
+               }
+       }
+
+       fp_off = sec->fp_off;
+       fp_ctl = fp_off ? 2 : 0; /* regnum=0, deref_p=(fp_off != 0), reg_p=0 */
+       if (dataword_count >= 2) {
+               UNSAFE_GET_USER_INC(fp_ctl, cur, dataword_size, Efault);
+               dataword_count--;
+               if (fp_ctl) {
+                       UNSAFE_GET_USER_INC(fp_off, cur, dataword_size, Efault);
+                       dataword_count--;
+               } else {
+                       /* Padding FP location info */
+                       fp_ctl = fp_off ? 2 : 0; /* re-deduce (see above) */
+               }
+       }
+
+       /* Reject trailing padding or unknown extra datawords */
+       if (dataword_count)
+               return -EINVAL;
+
+       fre->cfa_ctl    = cfa_ctl;
+       fre->cfa_off    = cfa_off;
+       fre->ra_ctl     = ra_ctl;
+       fre->ra_off     = ra_off;
+       fre->fp_ctl     = fp_ctl;
+       fre->fp_off     = fp_off;
+
+       return 0;
+
+Efault:
+       return -EFAULT;
+}
+
+static __always_inline int
+__read_fre_datawords(struct sframe_section *sec,
+                    struct sframe_fde_internal *fde,
+                    unsigned long cur,
+                    unsigned char dataword_count,
+                    unsigned char dataword_size,
+                    struct sframe_fre_internal *fre)
+{
+       unsigned char fde_type = SFRAME_V3_FDE_TYPE(fde->info2);
+
+       switch (fde_type) {
+       case SFRAME_FDE_TYPE_DEFAULT:
+               return __read_default_fre_datawords(sec, fde, cur,
+                                                   dataword_count,
+                                                   dataword_size,
+                                                   fre);
+       case SFRAME_FDE_TYPE_FLEX:
+               return __read_flex_fde_fre_datawords(sec, fde, cur,
+                                                    dataword_count,
+                                                    dataword_size,
+                                                    fre);
+       default:
+               return -EINVAL;
+       }
+}
+
 static __always_inline int __read_fre(struct sframe_section *sec,
                                      struct sframe_fde_internal *fde,
                                      unsigned long fre_addr,
                                      struct sframe_fre_internal *fre)
 {
-       unsigned char fde_type = SFRAME_V3_FDE_TYPE(fde->info2);
        unsigned char fde_pctype = SFRAME_V3_FDE_PCTYPE(fde->info);
        unsigned char fre_type = SFRAME_V3_FDE_FRE_TYPE(fde->info);
        unsigned char dataword_count, dataword_size;
-       s32 cfa_off, ra_off, fp_off;
        unsigned long cur = fre_addr;
        unsigned char addr_size;
        u32 ip_off;
@@ -236,75 +382,101 @@ static __always_inline int __read_fre(struct 
sframe_section *sec,
        if (cur + (dataword_count * dataword_size) > sec->fres_end)
                return -EFAULT;
 
-       /* TODO: Support for flexible FDEs not implemented yet. */
-       if (fde_type != SFRAME_FDE_TYPE_DEFAULT)
-               return -EINVAL;
+       fre->size       = addr_size + 1 + (dataword_count * dataword_size);
+       fre->ip_off     = ip_off;
+       fre->info       = info;
 
        if (!dataword_count) {
                /*
-                * A FRE without data words indicates RA undefined /
-                * outermost frame.
+                * A FRE without datawords indicates an outermost
+                * frame.  Zero-initialize CFA, RA, and FP location
+                * info, except for the CFA control word, so that
+                * neither sframe_init_cfa_rule_data() nor
+                * sframe_init_rule_data() fail.
                 */
-               cfa_off = 0;
-               ra_off  = 0;
-               fp_off  = 0;
-               goto done;
-       }
-
-       UNSAFE_GET_USER_INC(cfa_off, cur, dataword_size, Efault);
-       dataword_count--;
-
-       ra_off = sec->ra_off;
-       if (!ra_off && dataword_count) {
-               dataword_count--;
-               UNSAFE_GET_USER_INC(ra_off, cur, dataword_size, Efault);
-       }
+               fre->cfa_ctl    = (SFRAME_REG_SP << 3) | 1; /* regnum=SP, 
deref_p=0, reg_p=1 */
+               fre->cfa_off    = 0;
+               fre->ra_ctl     = 0;
+               fre->ra_off     = 0;
+               fre->fp_ctl     = 0;
+               fre->fp_off     = 0;
 
-       fp_off = sec->fp_off;
-       if (!fp_off && dataword_count) {
-               dataword_count--;
-               UNSAFE_GET_USER_INC(fp_off, cur, dataword_size, Efault);
+               return 0;
        }
 
-       if (dataword_count)
-               return -EINVAL;
-
-done:
-       fre->size       = addr_size + 1 + (dataword_count * dataword_size);
-       fre->ip_off     = ip_off;
-       fre->cfa_off    = cfa_off;
-       fre->ra_off     = ra_off;
-       fre->fp_off     = fp_off;
-       fre->info       = info;
-
-       return 0;
+       return __read_fre_datawords(sec, fde, cur, dataword_count, 
dataword_size, fre);
 
 Efault:
        return -EFAULT;
 }
 
-static __always_inline void
+static __always_inline int
 sframe_init_cfa_rule_data(struct unwind_user_cfa_rule_data *cfa_rule_data,
-                         unsigned char fre_info,
-                         s32 offset)
+                         u32 ctlword, s32 offset)
 {
-       if (SFRAME_V3_FRE_CFA_BASE_REG_ID(fre_info) == SFRAME_BASE_REG_FP)
-               cfa_rule_data->rule = UNWIND_USER_CFA_RULE_FP_OFFSET;
-       else
+       bool deref_p = SFRAME_V3_FLEX_FDE_CTRLWORD_DEREF_P(ctlword);
+       bool reg_p = SFRAME_V3_FLEX_FDE_CTRLWORD_REG_P(ctlword);
+       bool reserved_p = SFRAME_V3_FLEX_FDE_CTRLWORD_RESERVED_P(ctlword);
+       unsigned int regnum = SFRAME_V3_FLEX_FDE_CTRLWORD_REGNUM(ctlword);
+
+       if (reserved_p)
+               return -EINVAL;
+
+       /* CFA recovery rule must be register-based */
+       if (!reg_p)
+               return -EINVAL;
+
+       switch (regnum) {
+       case SFRAME_REG_SP:
                cfa_rule_data->rule = UNWIND_USER_CFA_RULE_SP_OFFSET;
+               break;
+       case SFRAME_REG_FP:
+               cfa_rule_data->rule = UNWIND_USER_CFA_RULE_FP_OFFSET;
+               break;
+       default:
+               cfa_rule_data->rule = UNWIND_USER_CFA_RULE_REG_OFFSET;
+               cfa_rule_data->regnum = regnum;
+       }
+
+       if (deref_p)
+               cfa_rule_data->rule |= UNWIND_USER_RULE_DEREF;
+
        cfa_rule_data->offset = offset;
+
+       return 0;
 }
 
-static __always_inline void
+static __always_inline int
 sframe_init_rule_data(struct unwind_user_rule_data *rule_data,
-                     s32 offset)
+                     u32 ctlword, s32 offset)
 {
-       if (offset) {
-               rule_data->rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF;
-               rule_data->offset = offset;
-       } else {
+       bool deref_p = SFRAME_V3_FLEX_FDE_CTRLWORD_DEREF_P(ctlword);
+       bool reg_p = SFRAME_V3_FLEX_FDE_CTRLWORD_REG_P(ctlword);
+       bool reserved_p = SFRAME_V3_FLEX_FDE_CTRLWORD_RESERVED_P(ctlword);
+
+       if (!ctlword && !offset) {
                rule_data->rule = UNWIND_USER_RULE_RETAIN;
+               return 0;
        }
+
+       if (reserved_p)
+               return -EINVAL;
+
+       if (reg_p) {
+               unsigned int regnum = 
SFRAME_V3_FLEX_FDE_CTRLWORD_REGNUM(ctlword);
+
+               rule_data->rule = UNWIND_USER_RULE_REG_OFFSET;
+               rule_data->regnum = regnum;
+       } else {
+               rule_data->rule = UNWIND_USER_RULE_CFA_OFFSET;
+       }
+
+       if (deref_p)
+               rule_data->rule |= UNWIND_USER_RULE_DEREF;
+
+       rule_data->offset = offset;
+
+       return 0;
 }
 
 static __always_inline int __find_fre(struct sframe_section *sec,
@@ -356,9 +528,15 @@ static __always_inline int __find_fre(struct 
sframe_section *sec,
                return -EINVAL;
        fre = prev_fre;
 
-       sframe_init_cfa_rule_data(&frame->cfa, fre->info, fre->cfa_off);
-       sframe_init_rule_data(&frame->ra, fre->ra_off);
-       sframe_init_rule_data(&frame->fp, fre->fp_off);
+       ret = sframe_init_cfa_rule_data(&frame->cfa, fre->cfa_ctl, 
fre->cfa_off);
+       if (ret)
+               return ret;
+       ret = sframe_init_rule_data(&frame->ra, fre->ra_ctl, fre->ra_off);
+       if (ret)
+               return ret;
+       ret = sframe_init_rule_data(&frame->fp, fre->fp_ctl, fre->fp_off);
+       if (ret)
+               return ret;
        frame->outermost = SFRAME_V3_FRE_RA_UNDEFINED_P(fre->info);
 
        return 0;
diff --git a/kernel/unwind/sframe.h b/kernel/unwind/sframe.h
index ed111fd0d702..1a2528e4b149 100644
--- a/kernel/unwind/sframe.h
+++ b/kernel/unwind/sframe.h
@@ -66,6 +66,7 @@ struct sframe_fda_v3 {
 #define SFRAME_V3_AARCH64_FDE_PAUTH_KEY(info)  (((info) >> 5) & 0x1)
 
 #define SFRAME_FDE_TYPE_DEFAULT                        0
+#define SFRAME_FDE_TYPE_FLEX                   1
 
 #define SFRAME_V3_FDE_TYPE_MASK                        0x1f
 #define SFRAME_V3_FDE_TYPE(info2)              ((info2) & 
SFRAME_V3_FDE_TYPE_MASK)
@@ -79,4 +80,9 @@ struct sframe_fda_v3 {
 #define SFRAME_V3_AARCH64_FRE_MANGLED_RA_P(info)       (((info) >> 7) & 0x1)
 #define SFRAME_V3_FRE_RA_UNDEFINED_P(info)             
(SFRAME_V3_FRE_DATAWORD_COUNT(info) == 0)
 
+#define SFRAME_V3_FLEX_FDE_CTRLWORD_REGNUM(data)       (((data) >> 3) & 0x1f)
+#define SFRAME_V3_FLEX_FDE_CTRLWORD_RESERVED_P(data)   (((data) >> 2) & 0x1)
+#define SFRAME_V3_FLEX_FDE_CTRLWORD_DEREF_P(data)      (((data) >> 1) & 0x1)
+#define SFRAME_V3_FLEX_FDE_CTRLWORD_REG_P(data)                ((data) & 0x1)
+
 #endif /* _SFRAME_H */
-- 
2.51.0


Reply via email to