[RFC 2/5] elf: Add SFrame stack tracing

Jens Remus jremus@linux.ibm.com
Wed Mar 19 15:32:44 GMT 2025


On 18.03.2025 14:03, claudiu.zissulescu-ianculescu@oracle.com wrote:
> From: Claudiu Zissulescu <claudiu.zissulescu-ianculescu@oracle.com>
> 
> This patch adds the necessary bits to enable stack tracing using
> SFrame.  In the case the new SFrame stack tracing procedure doesn't
> find SFrame related info, the stack tracing falls back on default
> Dwarf implementation.
> 
> The new SFrame stack tracing procedure is added to bebug/backtrace.c
> file, the support functions are added in sysdeps folder, namely
> sframe.h, read-sframe.c and read-sfame.h.
> 
> Signed-off-by: Claudiu Zissulescu <claudiu.zissulescu-ianculescu@oracle.com>

> diff --git a/debug/backtrace.c b/debug/backtrace.c

> @@ -20,6 +20,7 @@
>   #include <stdlib.h>
>   #include <unwind.h>
>   #include <unwind-link.h>
> +#include <sframe.h>
>   
>   struct trace_arg
>   {
> @@ -30,6 +31,38 @@ struct trace_arg
>     int size;
>   };
>   
> +/* Initialize the SFrame backtrace routine, and try to backtrace the
> +   current stack using SFrame info.  The return value of SFrame stack
> +   tracer must be larger than one to consider the SFrame backtrace
> +   valid. Otherwise, there may be the case that glibc is compiled
> +   using SFrame but the application not.   */

This is because DO_SFRAME_BACKTRACE() then managed to find SFrame
information for __backtrace() as the topmost and only frame in Glibc
and at least one further function from the application, right?

> +
> +#define DO_SFRAME_BACKTRACE(ARRAY, SIZE)			\
> +  do								\
> +    {								\
> +      int cnt;							\
> +      frame frame;						\
> +      frame.pc = getPC();					\
> +      frame.sp = getSP();					\
> +      frame.fp = (_Unwind_Ptr) __builtin_frame_address (0);	\

Why doesn't this require to be wrapped in a noinline helper getFP()?  If
there is some clever trickery involved , could you maybe add a comment?

> +      cnt = stacktrace_sframe (ARRAY, SIZE, &frame);		\
> +      if (cnt > 1)						\
> +	return cnt;						\
> +    }								\
> +  while(0)
> +
> +static _Unwind_Ptr  __attribute__ ((noinline))
> +getPC (void)
> +{
> +  return (_Unwind_Ptr) __builtin_return_address (0);

Clever to wrap this in a noinline helper, to use this builtin to obtain
the PC in __backchain() (at invocation of the helper).

Although not required on x86-64, AArch64, and s390x: Would it make sense
to use __builtin_extract_return_addr() in addition?

> +}
> +
> +static _Unwind_Ptr  __attribute__ ((noinline))
> +getSP (void)
> +{
> +  return (_Unwind_Ptr) __builtin_dwarf_cfa();

This only works for architectures that define their CFA as SP at
call site.  On s390x the CFA is defined as SP at call site +160.
I will have to make either this helber architecture dependent or
introduce an architecture dependent SP value offset from CFA (e.g.
SFRAME_SP_VAL_OFFSET).

> +}
> +
>   static _Unwind_Reason_Code
>   backtrace_helper (struct _Unwind_Context *ctx, void *a)
>   {
> @@ -72,7 +105,13 @@ __backtrace (void **array, int size)
>        .cnt = -1
>       };
>   
> -  if (size <= 0 || arg.unwind_link == NULL)
> +  if (size <= 0)
> +    return 0;
> +

It's kind of obvious, but maybe add the following comment for those that
do not know SFrame:

   /* Thy the SFrame stack tracer.  */

> +  DO_SFRAME_BACKTRACE (array, size);
> +
> +  /* Try the dwarf unwinder.  */
> +  if (arg.unwind_link == NULL)
>       return 0;
>   
>     UNWIND_LINK_PTR (arg.unwind_link, _Unwind_Backtrace)

> diff --git a/sysdeps/generic/sframe.c b/sysdeps/generic/sframe.c

> @@ -0,0 +1,140 @@
> +/* Copyright (C) 2025 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public License as
> +   published by the Free Software Foundation; either version 2.1 of the
> +   License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +#include <sframe-read.h>
> +#include <stdlib.h>
> +#include <dlfcn.h>
> +#include <unwind.h>
> +#include <uw-sigframe.h>
> +
> +#if DLFO_STRUCT_HAS_SFRAME

IIUC this makes Glibc backtrace() use of SFrame for stack tracing solely
depend on whether an architecture has enabled DLFO_STRUCT_HAS_SFRAME.
Both x86-64 and AArch64 enable this unconditionally with your subsequent
patches.

Wouldn't it make sense to not enable it if Glibc is not compiled with
SFrame stack trace information?  Otherwise __backtrace() would always
attempt to use SFrame first with stacktrace_sframe() failing to locate
any SFrame information for __backtrace() itself.

In addition I wonder whether it would be good to allow to build Glibc
with SFrame stack trace information but disable the use in __backtrace().

> +
> +static inline uint64_t
> +read_stack_value (uint64_t loc)
> +{
> +  uint64_t value = *((uint64_t *) loc);
> +  return value;
> +}
> +
> +/* Backtrace the stack and collect the stacktrace given SFrame info.
> +   If successful, store the return addresses in RA_LST. The SIZE
> +   argument specifies the maximum number of return addresses that can
> +   be stored in RA_LST and contains the number of the addresses
> +   collected.  */
> +
> +int
> +stacktrace_sframe (void **ra_lst, int count, frame *frame)
> +{
> +  _Unwind_Ptr sframe_vma, cfa, return_addr, ra_stack_loc, rfp_stack_loc, pc;
> +  int cfa_offset, rfp_offset, ra_offset, i;

Why rfp_stack_loc and rfp_offset instead of fp_stack_loc and fp_offset?

> +  sframe_frame_row_entry fred, *frep = &fred;
> +
> +  if (!ra_lst || !count)
> +    return 0;
> +
> +  for (i = 0; i < count; i++)
> +    {
> +      _Unwind_Reason_Code err;
> +      struct dl_find_object data;
> +      sframe_decoder_ctx decoder_context, *dctx = &decoder_context;
> +
> +      /* Clean decoder context.  */
> +      memset (dctx, 0, sizeof (sframe_decoder_ctx));
> +
> +      /* Load and set up the SFrame stack trace info for pc.  */
> +      if (_dl_find_object ((void *) frame->pc, &data) < 0)
> +	return i;
> +
> +      sframe_vma = (_Unwind_Ptr) data.dlfo_sframe;
> +      if (!sframe_vma)
> +	{
> +#ifdef MD_DECODE_SIGNAL_FRAME
> +	  /* I cannot find a valid SFrame section. Check if it is a
> +	     signal frame.  */
> +	  if (MD_DECODE_SIGNAL_FRAME (frame) == _URC_NO_REASON)
> +	    {
> +	      ra_lst[i] = (void *) frame->pc;
> +	      continue;
> +	    }
> +#endif
> +	  return i;
> +	}
> +
> +      /* Decode the specified SFrame buffer populate sframe's decoder
> +	 context.  */
> +      if (sframe_decode (dctx, (char *) data.dlfo_sframe) == _URC_END_OF_STACK)
> +	return i;
> +
> +      pc = frame->pc - sframe_vma;
> +      /* Find the SFrame Row Entry which contains the PC.  */
> +      if (sframe_find_fre (dctx, pc, frep) == _URC_END_OF_STACK)
> +	{
> +#ifdef MD_DECODE_SIGNAL_FRAME
> +	  /* I cannot find any FREs, try to see if it is a signal
> +	     frame, and if so decode it.  */
> +	  if (MD_DECODE_SIGNAL_FRAME (frame) == _URC_NO_REASON)
> +	    {
> +	      ra_lst[i] = (void *) frame->pc;
> +	      continue;
> +	    }
> +#endif
> +	  return i;
> +	}
> +
> +      /* Get the CFA offset from the FRE.  */
> +      cfa_offset = sframe_fre_get_cfa_offset (dctx, frep);
> +
> +      /* Get the base reg id from the FRE info.  */

The comment is misleading, as the computed value is the CFA.  Maybe:

       /* Get CFA using base reg id from the FRE info.  */

> +      cfa = ((sframe_fre_get_base_reg_id (frep)
> +	      == SFRAME_BASE_REG_SP) ? frame->sp : frame->fp) + cfa_offset;
> +
> +      /* Get the RA offset from the FRE.  */
> +      ra_offset = sframe_fre_get_ra_offset (dctx, frep);
> +
> +      ra_stack_loc = cfa + ra_offset;
> +      return_addr = read_stack_value (ra_stack_loc);

On s390x the RA (and FP) may be saved in registers in the topost frame,
e.g. when in a leaf function.  In that case s390x will encode the DWARF
register number in the SFrame RA/FP offset.  The good thing is that
neither the caller of backtrace() nor __backtrace() itself are leaf
functions, so that this special case may not occur in the this Glibc
use case.  I would need to add a check though, that the returned offset
is not an encoded DWARF register number on s390x.

> +
> +      ra_lst[i] = (void *)return_addr;
> +
> +      /* Set up for the next frame.  */
> +      /* Get the FP offset from the FRE.  If the offset is invalid,
> +	 sets errp.  */
> +      rfp_offset = sframe_fre_get_fp_offset (dctx, frep, &err);
> +      if (err == _URC_NO_REASON)
> +	{
> +	  /* Frame is valid, get the value stored in the stack
> +	     location.  */
> +	  rfp_stack_loc = cfa + rfp_offset;
> +	  frame->fp = read_stack_value (rfp_stack_loc);

Same as for RA above.

> +	}
> +
> +      frame->sp = cfa;

Same as for GetSP() above.  I guess introducing an architecture
dependent SP value offset from CFA (e.g. SFRAME_SP_VAL_OFFSET) would be
the easiest solution?  On architectures, such as x86-64 and AArch64,
where it would be defined to 0 the compiler would optimize the offset
away.  What do you think?

> +      frame->pc = return_addr;
> +    }
> +  return i;
> +}
> +#else

#else /* !DLFO_STRUCT_HAS_SFRAME  */

> +/* Dummy function called when SFrame is not available for a target.  */
> +int
> +stacktrace_sframe (void **ra_lst __attribute__ ((__unused__)),
> +		   int count __attribute__ ((__unused__)),
> +		   frame *frame __attribute__ ((__unused__)))
> +{
> +  return 0;
> +}
> +#endif /* DLFO_STRUCT_HAS_SFRAME  */
> +
> +libc_hidden_def (stacktrace_sframe)

> diff --git a/sysdeps/generic/sframe.h b/sysdeps/generic/sframe.h

> @@ -0,0 +1,359 @@
> +/* SFrame format description.
> +   Copyright (C) 2022-2025 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program; see the file COPYING.  If not see
> +   <http://www.gnu.org/licenses/>.  */

Would it make sense to add a note that this is a copy of GNU Binutils
include/sframe.h with only minor adaptions and additions and therefore
should be kept in sync?

> +
> +#ifndef	_SFRAME_H
> +#define	_SFRAME_H

Thanks and regards,
Jens
-- 
Jens Remus
Linux on Z Development (D3303)
+49-7031-16-1128 Office
jremus@de.ibm.com

IBM

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Böblingen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/



More information about the Libc-alpha mailing list