]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gas: sframe: Represent .cfi_undefined RA as FRE without offsets
authorJens Remus <jremus@linux.ibm.com>
Wed, 15 Oct 2025 15:45:48 +0000 (17:45 +0200)
committerJens Remus <jremus@linux.ibm.com>
Wed, 15 Oct 2025 15:45:48 +0000 (17:45 +0200)
In DWARF CFI an "undefined" register rule for the return address (RA)
register indicates that there is no return address and the stack trace
is complete.

Represent DW_CFA_undefined as SFrame FRE without any offsets, so that a
stack tracer implementation can use this as indication that an outermost
frame has been reached and the stack trace is complete.

This representation is backward compatible, as existing stack tracers
should already deal with the case, that an SFrame FRE a so far invalid
offset count of zero and stop the trace.

include/
* sframe.h (SFRAME_V2_FRE_RA_UNDEFINED_P): New macro to test
FRE info word for RA undefined (FRE without any offsets).

binutils/
* NEWS: Mention SFrame can represent an undefined RA as FRE
without any offsets.

gas/
* gen-sframe.h (struct sframe_row_entry): Add ra_undefined_p
flag.
* gen-sframe.c (sframe_row_entry_new): Initialize ra_undefined_p
flag to not set.
(sframe_row_entry_initialize): Treat ra_undefined_p flag as
sticky.
(sframe_fre_set_ra_track): Reset ra_undefined_p flag.
(sframe_xlate_do_restore): Reset ra_undefined_p flag to saved
state.
(sframe_xlate_do_same_value): Reset ra_undefined_p flag.
(sframe_xlate_do_cfi_undefined): For RA set ra_undefined_p flag.
(output_sframe_row_entry): Represent RA undefined as SFrame FRE
without any offsets and FRE info word fields zeroed.
* NEWS: Mention assembler represents .cfi_undefined RA in SFrame
as FRE without any offsets.

libsframe/
* doc/sframe-spec.texi (Changes from Version 1 to Version 2):
Mention that a SFrame FRE without any offsets flag indicates an
outermost frame with an undefined RA.
(fre_offset_count): Document that a FRE offset count of zero
indicates an outermost frame with an undefined RA.
* sframe.c (sframe_get_fre_ra_undefined_p): Use macro
SFRAME_V2_FRE_RA_UNDEFINED_P.
(sframe_fre_get_fp_offset, sframe_fre_get_ra_offset): Do not
return fixed FP/RA offset if RA undefined.
* sframe-dump.c (dump_sframe_func_with_fres): Show FRE without
any offsets as "RA undefined".

gas/testsuite/
* gas/cfi-sframe/cfi-sframe.exp: Run tests for .cfi_undefined RA
on AArch64, s390x, and x86-64.
* gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.d: Add test
for .cfi_undefined RA on AArch64.
* gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.s: Likewise.
* as/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.d: Add test
for .cfi_undefined RA on s390x.
* gas/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.s: Likewise.
* gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.d: Add test
for .cfi_undefined RA on x86-64.
* gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.s: Likewise.

Signed-off-by: Jens Remus <jremus@linux.ibm.com>
15 files changed:
binutils/NEWS
gas/NEWS
gas/gen-sframe.c
gas/gen-sframe.h
gas/testsuite/gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.d [new file with mode: 0644]
gas/testsuite/gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.s [new file with mode: 0644]
gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.d [new file with mode: 0644]
gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.s [new file with mode: 0644]
gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.d [new file with mode: 0644]
gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.s [new file with mode: 0644]
gas/testsuite/gas/cfi-sframe/cfi-sframe.exp
include/sframe.h
libsframe/doc/sframe-spec.texi
libsframe/sframe-dump.c
libsframe/sframe.c

index 0a4ed3bcc430bb6890311ed88550b87fbac2bf4c..c7c44a708d278336f50f98fa95d69c09a16b209b 100644 (file)
@@ -1,5 +1,10 @@
 -*- text -*-
 
+* SFrame stack trace format now represents an undefined return address as
+  an SFrame FRE without any offsets.  libsframe provides a new API to test
+  for RA undefined, which is used when dumping SFrame information (e.g. using
+  objdump and readelf) to show such FREs as "RA undefined".
+
 * Add --got-contents option to readelf to display the contents of
   Global Offset Table (GOT) sections.
 
index 4de0cfbcc916c73e0b8beb688b276c488062a7d4..4bd3d747aa5b7c5ab115c67d97364d133db01775 100644 (file)
--- a/gas/NEWS
+++ b/gas/NEWS
@@ -1,5 +1,8 @@
 -*- text -*-
 
+* Emit an SFrame FRE with zero offsets to convey an undefined return address
+  in the SFrame stack trace format.
+
 * ELF targets can now have section entity size specified for arbitrary
   sections, using the new attribute letter 'E'.
 
index 8643ee8731e85f5c391c2b23d4a7cfe7d4221b08..b1aa8072410b4d9c8dc4beee64da875d323de55a 100644 (file)
@@ -164,6 +164,7 @@ sframe_fre_set_ra_track (struct sframe_row_entry *fre, offsetT ra_offset)
 {
   fre->ra_loc = SFRAME_FRE_ELEM_LOC_STACK;
   fre->ra_offset = ra_offset;
+  fre->ra_undefined_p = false;
   fre->merge_candidate = false;
 }
 
@@ -511,6 +512,8 @@ sframe_row_entry_new (void)
      initialize it in sframe_row_entry_initialize () with the sticky
      bit if set.  */
   fre->mangled_ra_p = false;
+  /* Reset the RA undefined status by to zero by default.  */
+  fre->ra_undefined_p = false;
 
   return fre;
 }
@@ -552,6 +555,7 @@ output_sframe_row_entry (symbolS *fde_start_addr,
   unsigned int fre_num_offsets;
   unsigned int fre_offset_size;
   unsigned int fre_base_reg;
+  bool fre_mangled_ra_p;
   expressionS exp;
   unsigned int fre_addr_size;
 
@@ -581,14 +585,30 @@ output_sframe_row_entry (symbolS *fde_start_addr,
     }
 
   /* Create the fre_info using the CFA base register, number of offsets and max
-     size of offset in this frame row entry.  */
-  fre_base_reg = get_fre_base_reg_id (sframe_fre);
-  fre_num_offsets = get_fre_num_offsets (sframe_fre);
-  fre_offset_size = sframe_get_fre_offset_size (sframe_fre);
+     size of offset in this frame row entry.  Represent RA undefined as FRE
+     without any offsets and all FRE info word fields zeroed.  */
+  if (sframe_fre->ra_undefined_p)
+    {
+      fre_base_reg = 0;
+      fre_num_offsets = 0;
+      fre_offset_size = 0;
+      fre_mangled_ra_p = 0;
+    }
+  else
+    {
+      fre_base_reg = get_fre_base_reg_id (sframe_fre);
+      fre_num_offsets = get_fre_num_offsets (sframe_fre);
+      fre_offset_size = sframe_get_fre_offset_size (sframe_fre);
+      fre_mangled_ra_p = sframe_fre->mangled_ra_p;
+    }
   fre_info = sframe_set_fre_info (fre_base_reg, fre_num_offsets,
-                                 fre_offset_size, sframe_fre->mangled_ra_p);
+                                 fre_offset_size, fre_mangled_ra_p);
   out_one (fre_info);
 
+  /* Represent RA undefined as FRE without any offsets.  */
+  if (sframe_fre->ra_undefined_p)
+    return;
+
   idx = sframe_fre_offset_func_map_index (fre_offset_size);
   gas_assert (idx < SFRAME_FRE_OFFSET_FUNC_MAP_INDEX_MAX);
 
@@ -944,6 +964,10 @@ sframe_row_entry_initialize (struct sframe_row_entry *cur_fre,
   /* Treat RA mangling as a sticky bit.  It retains its value until another
      .cfi_negate_ra_state is seen.  */
   cur_fre->mangled_ra_p = prev_fre->mangled_ra_p;
+  /* Treat RA undefined as a sticky bit.  It retains its value until a
+     .cfi_offset RA, .cfi_register RA, .cfi_restore RA, or .cfi_same_value RA
+     is seen.  */
+  cur_fre->ra_undefined_p = prev_fre->ra_undefined_p;
 }
 
 /* Return SFrame register name for SP, FP, and RA, or NULL if other.  */
@@ -1319,6 +1343,7 @@ sframe_xlate_do_restore (struct sframe_xlate_ctx *xlate_ctx,
       gas_assert (cur_fre);
       cur_fre->ra_loc = cie_fre->ra_loc;
       cur_fre->ra_offset = cie_fre->ra_offset;
+      cur_fre->ra_undefined_p = cie_fre->ra_undefined_p;
       cur_fre->merge_candidate = false;
     }
   return SFRAME_XLATE_OK;
@@ -1665,9 +1690,15 @@ sframe_xlate_do_cfi_escape (const struct sframe_xlate_ctx *xlate_ctx,
 /* Translate DW_CFA_undefined into SFrame context.
 
    DW_CFA_undefined op indicates that from now on, the previous value of
-   register can’t be restored anymore.  In SFrame stack trace, we cannot
-   represent such a semantic.  So, we skip generating an SFrame FDE for this,
-   when a register of interest is used with DW_CFA_undefined.
+   register can’t be restored anymore.  In DWARF, for the return address (RA)
+   register, this indicates to an unwinder that there is no return address
+   and the unwind is complete.
+
+   In SFrame, represent the use of the RA register with DW_CFA_undefined as
+   SFrame FRE without any offsets.  Stack tracers can use this as indication
+   that an outermost frame has been reached and the stack trace is complete.
+   The use of other registers of interest with  DW_CFA_undefined cannot be
+   represented in SFrame.  Therefore skip generating an SFrame FDE.
 
    Return SFRAME_XLATE_OK if success.  */
 
@@ -1676,15 +1707,24 @@ sframe_xlate_do_cfi_undefined (const struct sframe_xlate_ctx *xlate_ctx ATTRIBUT
                               const struct cfi_insn_data *cfi_insn)
 {
   if (cfi_insn->u.r == SFRAME_CFA_FP_REG
-      || cfi_insn->u.r == SFRAME_CFA_RA_REG
       || cfi_insn->u.r == SFRAME_CFA_SP_REG)
     {
       as_warn (_("no SFrame FDE emitted; %s reg %u in .cfi_undefined"),
               sframe_register_name (cfi_insn->u.r), cfi_insn->u.r);
       return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented.  */
     }
+  else if (cfi_insn->u.r == SFRAME_CFA_RA_REG)
+    {
+      /* Represent RA undefined (i.e. outermost frame) as FRE without any
+        offsets.  */
+      struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre;
+
+      gas_assert (cur_fre);
+      /* Set RA undefined status bit.  */
+      cur_fre->ra_undefined_p = true;
+      cur_fre->merge_candidate = false;
+    }
 
-  /* Safe to skip.  */
   return SFRAME_XLATE_OK;
 }
 
@@ -1732,6 +1772,7 @@ sframe_xlate_do_same_value (const struct sframe_xlate_ctx *xlate_ctx,
     {
       cur_fre->ra_loc = SFRAME_FRE_ELEM_LOC_REG;
       cur_fre->ra_offset = 0;
+      cur_fre->ra_undefined_p = false;
       cur_fre->merge_candidate = false;
     }
   else if (cfi_insn->u.r == SFRAME_CFA_FP_REG)
index 8ad521b5cbee2e4d22ec0fffd176dfd51348f6ca..cf9f5987bf5fd5ce8c28d43070fd22809b8944a4 100644 (file)
@@ -65,6 +65,9 @@ struct sframe_row_entry
   /* Whether the return address is mangled with pauth code.  */
   bool mangled_ra_p;
 
+  /* Whether RA is undefined.  */
+  bool ra_undefined_p;
+
   /* Track CFA base (architectural) register ID.  */
   unsigned int cfa_base_reg;
   /* Offset from the CFA base register for recovering CFA.  */
diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.d b/gas/testsuite/gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.d
new file mode 100644 (file)
index 0000000..f6bd6d7
--- /dev/null
@@ -0,0 +1,20 @@
+#as: --gsframe
+#objdump: --sframe=.sframe
+#name: SFrame generation on aarch64 - .cfi_undefined RA
+#...
+Contents of the SFrame section .sframe:
+  Header :
+
+    Version: SFRAME_VERSION_2
+    Flags: SFRAME_F_FDE_FUNC_START_PCREL
+    Num FDEs: 1
+    Num FREs: 4
+
+  Function Index :
+
+    func idx \[0\]: pc = 0x0, size = 16 bytes
+    STARTPC +CFA +FP +RA +
+    0+0000 +sp\+0 +u +u +
+    0+0004 +sp\+16 +c\-16 +c\-8 +
+    0+0008 +RA undefined
+    0+000c +RA undefined
diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.s b/gas/testsuite/gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.s
new file mode 100644 (file)
index 0000000..7b28ab8
--- /dev/null
@@ -0,0 +1,13 @@
+       .cfi_startproc
+       stp     fp, lr, [sp, #-16]!
+       .cfi_def_cfa_offset 16
+       .cfi_offset 29, -16
+       .cfi_offset 30, -8
+       nop
+       .cfi_undefined 30
+       ldp     fp, lr, [sp], #16
+       .cfi_restore 20
+       .cfi_restore 19
+       .cfi_def_cfa_offset 0
+       ret     lr
+       .cfi_endproc
diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.d b/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.d
new file mode 100644 (file)
index 0000000..dfd47e3
--- /dev/null
@@ -0,0 +1,21 @@
+#name: SFrame generation on s390x - .cfi_undefined RA
+#as: --gsframe
+#objdump: --sframe=.sframe
+#...
+Contents of the SFrame section .sframe:
+
+  Header :
+
+    Version: SFRAME_VERSION_2
+    Flags: SFRAME_F_FDE_FUNC_START_PCREL
+    Num FDEs: 1
+    Num FREs: 4
+
+  Function Index :
+
+    func idx \[0\]: pc = 0x0, size = 18 bytes
+    STARTPC +CFA +FP +RA +
+    0+0000 +sp\+160 +u +u +
+    0+0006 +sp\+160 +u +c\-48 +
+    0+000a +RA undefined
+    0+0010 +sp\+160 +u +u +
diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.s b/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.s
new file mode 100644 (file)
index 0000000..2117a4c
--- /dev/null
@@ -0,0 +1,11 @@
+       .cfi_startproc
+       stmg    %r14,%r15,112(%r15)
+       .cfi_offset 14, -48
+       .cfi_offset 15, -40
+       nop
+       .cfi_undefined 14
+       lmg     %r14,%r15,112(%r15)
+       .cfi_restore 15
+       .cfi_restore 14
+       br      %r14
+       .cfi_endproc
diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.d b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.d
new file mode 100644 (file)
index 0000000..a635c3c
--- /dev/null
@@ -0,0 +1,22 @@
+#as: --gsframe -O0
+#objdump: --sframe=.sframe
+#name: SFrame generation on x86_64 - .cfi_undefined RA
+#...
+Contents of the SFrame section .sframe:
+
+  Header :
+
+    Version: SFRAME_VERSION_2
+    Flags: SFRAME_F_FDE_FUNC_START_PCREL
+    CFA fixed RA offset: \-8
+    Num FDEs: 1
+    Num FREs: 4
+
+  Function Index :
+
+    func idx \[0\]: pc = 0x0, size = 6 bytes
+    STARTPC +CFA +FP +RA +
+    0+0000 +sp\+8 +u +f +
+    0+0001 +sp\+16 +c\-16 +f +
+    0+0004 +fp\+16 +c\-16 +f +
+    0+0005 +RA undefined
diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.s b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.s
new file mode 100644 (file)
index 0000000..0f5b9c7
--- /dev/null
@@ -0,0 +1,11 @@
+       .cfi_startproc
+       pushq   %rbp
+       .cfi_def_cfa_offset 16
+       .cfi_offset 6, -16
+       movq    %rsp, %rbp
+       .cfi_def_cfa_register 6
+       nop
+       .cfi_undefined 16
+       .cfi_def_cfa 7, 8
+       ret
+       .cfi_endproc
index 7c93164c4c63b19e6604456f72eb274f72bc2ef3..208285165469ad78cf8ffef6a352803ee672fe51 100644 (file)
@@ -66,6 +66,7 @@ if { [istarget "x86_64-*-*"] && [gas_sframe_check] } then {
        run_dump_test "cfi-sframe-x86_64-empty-2"
        run_dump_test "cfi-sframe-x86_64-empty-3"
        run_dump_test "cfi-sframe-x86_64-empty-4"
+       run_dump_test "cfi-sframe-x86_64-ra-undefined-1"
        set ASFLAGS "$old_ASFLAGS"
     }
 }
@@ -77,6 +78,7 @@ if { [istarget "aarch64*-*-*"] && [gas_sframe_check] } then {
     run_dump_test "cfi-sframe-aarch64-3"
     run_dump_test "cfi-sframe-aarch64-4"
     run_dump_test "cfi-sframe-aarch64-pac-ab-key-1"
+    run_dump_test "cfi-sframe-aarch64-ra-undefined-1"
 }
 
 # s390x specific tests
@@ -91,4 +93,5 @@ if { [istarget "s390x*-*-*"] && [gas_sframe_check] } then {
     run_dump_test "cfi-sframe-s390x-fpra-offset-2"
     run_dump_test "cfi-sframe-s390x-fpra-register-1"
     run_dump_test "cfi-sframe-s390x-fpra-register-2"
+    run_dump_test "cfi-sframe-s390x-ra-undefined-1"
 }
index 7523adbef3a879abbdf644132cb33339f6c69ff7..127bf375057701188b6c7543d479bf056f9c48e6 100644 (file)
@@ -287,6 +287,7 @@ typedef struct sframe_fre_info
 #define SFRAME_V1_FRE_OFFSET_COUNT(data)         (((data) >> 1) & 0xf)
 #define SFRAME_V1_FRE_OFFSET_SIZE(data)                  (((data) >> 5) & 0x3)
 #define SFRAME_V1_FRE_MANGLED_RA_P(data)         (((data) >> 7) & 0x1)
+#define SFRAME_V2_FRE_RA_UNDEFINED_P(data)       (SFRAME_V1_FRE_OFFSET_COUNT (data) == 0)
 
 /* SFrame Frame Row Entry definitions.
 
index 73060ff8451e9964c821cd3a0815c100f6474532..689cc28add7fd62f7d5b7aeb3a320688b0df2887 100644 (file)
@@ -171,6 +171,11 @@ by CFA offset alignment factor and then revert CFA offset adjustment).
  @end itemize
 @item
 [Errata 1] An ELF SFrame section has the type SHT_GNU_SFRAME.
+@item
+[Errata 2] An SFrame FRE info word offset count of zero indicates that the
+return address (RA) is undefined for the range of PCs covered by the SFrame FRE.
+A stack tracer may use this as indication that an outermost frame has been
+reached and the stack trace is complete.
 @end itemize
 
 SFrame version 1 is now obsolete and should not be used.
@@ -777,7 +782,10 @@ SFRAME_FRE_OFFSET_4B.
 @item 1-4
 @tab @code{fre_offset_count}
 @tab A max value of 15 is allowed.  Typically, a value of upto 3 is sufficient
-for most ABIs to track all three of CFA, FP and RA.
+for most ABIs to track all three of CFA, FP and RA.  A value of zero indicates
+that the return address (RA) is undefined.  A stack tracer may use this as
+indication that an outermost frame has been reached and the stack trace is
+complete.
 
 @item 0
 @tab @code{fre_cfa_base_reg_id}
index d55d3847194e47227023f91b9a1161d8370d249f..1290966439f2a5e395c71f365eaf0881f541fca9 100644 (file)
@@ -130,6 +130,7 @@ dump_sframe_func_with_fres (sframe_decoder_ctx *sfd_ctx,
   uint64_t func_start_pc_vma = 0;
   uint64_t fre_start_pc_vma = 0;
   const char *base_reg_str[] = {"fp", "sp"};
+  bool ra_undefined_p = false;
   int32_t cfa_offset = 0;
   int32_t fp_offset = 0;
   int32_t ra_offset = 0;
@@ -180,15 +181,25 @@ dump_sframe_func_with_fres (sframe_decoder_ctx *sfd_ctx,
                          : func_start_pc_vma + fre.fre_start_addr);
 
       /* FIXME - fixup the err caching in array.
-        assert no error for base reg id.  */
+        assert no error for base reg id and RA undefined.  */
       base_reg_id = sframe_fre_get_base_reg_id (&fre, &err[0]);
+      ra_undefined_p = sframe_fre_get_ra_undefined_p (sfd_ctx, &fre, &err[0]);
       cfa_offset = sframe_fre_get_cfa_offset (sfd_ctx, &fre, &err[0]);
       fp_offset = sframe_fre_get_fp_offset (sfd_ctx, &fre, &err[1]);
       ra_offset = sframe_fre_get_ra_offset (sfd_ctx, &fre, &err[2]);
 
-      /* Dump CFA info.  */
+      /* Dump VMA.  */
       printf ("\n");
       printf ("    %016"PRIx64, fre_start_pc_vma);
+
+      /* Dump RA undefined (FRE without any offsets).  */
+      if (ra_undefined_p)
+       {
+         printf ("  RA undefined");
+         continue;
+       }
+
+      /* Dump CFA info.  */
       sprintf (temp, "%s+%d", base_reg_str[base_reg_id], cfa_offset);
       printf ("  %-10s", temp);
 
index bd39780a9135ae521a2c772f507ed40e6398e2e9..9d20f2e2d711096b413d4cbdac3b5a1599125c06 100644 (file)
@@ -134,7 +134,7 @@ sframe_get_fre_ra_mangled_p (uint8_t fre_info)
 static bool
 sframe_get_fre_ra_undefined_p (uint8_t fre_info)
 {
-  return SFRAME_V1_FRE_OFFSET_COUNT (fre_info) == 0;
+  return SFRAME_V2_FRE_RA_UNDEFINED_P (fre_info);
 }
 
 /* Access functions for info from function descriptor entry.  */
@@ -729,7 +729,8 @@ sframe_fre_get_fp_offset (sframe_decoder_ctx *dctx,
   int8_t fp_offset = sframe_decoder_get_fixed_fp_offset (dctx);
   /* If the FP offset is not being tracked, return the fixed FP offset
      from the SFrame header.  */
-  if (fp_offset != SFRAME_CFA_FIXED_FP_INVALID)
+  if (fp_offset != SFRAME_CFA_FIXED_FP_INVALID
+      && !sframe_get_fre_ra_undefined_p (fre->fre_info))
     {
       if (errp)
        *errp = 0;
@@ -760,7 +761,8 @@ sframe_fre_get_ra_offset (sframe_decoder_ctx *dctx,
   int8_t ra_offset = sframe_decoder_get_fixed_ra_offset (dctx);
   /* If the RA offset was not being tracked, return the fixed RA offset
      from the SFrame header.  */
-  if (ra_offset != SFRAME_CFA_FIXED_RA_INVALID)
+  if (ra_offset != SFRAME_CFA_FIXED_RA_INVALID
+      && !sframe_get_fre_ra_undefined_p (fre->fre_info))
     {
       if (errp)
        *errp = 0;