aboutsummaryrefslogtreecommitdiff
path: root/sysdeps/generic/sframe.c
blob: ba0830da3d3063358138559ad80bc3ac881b453f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/* 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>
#include <ldsodefs.h>

/* Some arches like s390x needs an offset to correct the value where
   SP is located in relation to CFA.  */
#ifndef SFRAME_SP_VAL_OFFSET
#define SFRAME_SP_VAL_OFFSET 0
#endif

static inline _Unwind_Ptr
read_stack_value (_Unwind_Ptr loc)
{
  _Unwind_Ptr value = *((_Unwind_Ptr *) loc);
  return value;
}

/* Helper to avoid PLT call in libc.  Fixes elf/check-localplt
   errors.  */

static int
_dl_find_object_helper (void *address, struct dl_find_object *result)
{
  return GLRO (dl_find_object) (address, result);
}

/* 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, fp_stack_loc, pc,
    frame_ptr;
  int cfa_offset, fp_offset, ra_offset, i;
  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_helper ((void *) frame->pc, &data) < 0)
	/* Force fallback to DWARF stacktracer.  */
	return 0;

      sframe_vma = (_Unwind_Ptr) data.dlfo_sframe;
      if (!sframe_vma || !(data.dlfo_flags & DLFO_FLAG_SFRAME))
	{
#ifdef MD_DECODE_SIGNAL_FRAME
	  /* If there is no valid SFrame section or SFrame section is
	     corrupted then check if it is a signal frame.  */
	  if (MD_DECODE_SIGNAL_FRAME (frame) == _URC_NO_REASON)
	    {
	      ra_lst[i] = (void *) frame->pc;
	      continue;
	    }
#endif
	  /* Force fallback to DWARF stacktracer.  */
	  return 0;
	}

      /* Decode the specified SFrame buffer populate sframe's decoder
	 context.  */
      if (__sframe_decode (dctx, (char *) data.dlfo_sframe) != _URC_NO_REASON)
	/* Force fallback to DWARF stacktracer.  */
	return 0;

      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
	  /* If there are no valid FREs, check 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
#ifdef MD_DETECT_OUTERMOST_FRAME
	  if (MD_DETECT_OUTERMOST_FRAME (frame) == _URC_END_OF_STACK)
	    return i;
#endif
	  /* Force fallback to DWARF stacktracer.  */
	  return 0;
	}

      /* Get the CFA offset from the FRE.  If offset is unavailable,
	 sets err.  */
      cfa_offset = __sframe_fre_get_cfa_offset (dctx, frep, &err);
      if (err != _URC_NO_REASON)
	/* Force fallback to DWARF stacktracer.  */
	return 0;

      /* 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.  If the offset is
	 unavailable, sets err.  */
      ra_offset = __sframe_fre_get_ra_offset (dctx, frep, &err);
      if (err != _URC_NO_REASON)
	/* Force fallback to DWARF stacktracer.  */
	return 0;

      /* RA offset is available, get the value stored in the stack
	 location.  */
      ra_stack_loc = cfa + ra_offset;
      return_addr = read_stack_value (ra_stack_loc);

      ra_lst[i] = (void *) return_addr;

      /* Get the FP offset from the FRE.  If the offset is
	 unavailable, sets err.  */
      fp_offset = __sframe_fre_get_fp_offset (dctx, frep, &err);
      frame_ptr = frame->fp;
      if (err == _URC_NO_REASON)
	{
	  /* FP offset is available, get the value stored in the stack
	     location.  */
	  fp_stack_loc = cfa + fp_offset;
	  frame_ptr = read_stack_value (fp_stack_loc);
	}

      /* Set up for the next frame.  */
      frame->fp = frame_ptr;
      frame->sp = cfa + SFRAME_SP_VAL_OFFSET;
      frame->pc = return_addr;
    }
  return i;
}

libc_hidden_def (__stacktrace_sframe);

/* A noinline helper used to obtain the caller's current PC.  */

_Unwind_Ptr  __attribute__ ((noinline))
__getPC (void)
{
  return (_Unwind_Ptr)
    __builtin_extract_return_addr (__builtin_return_address (0));
}

libc_hidden_def (__getPC);

/* A noinline helper used to obtain the caller's current SP.  It
   mimics gcc14's __builtin_stack_address() functionality.  */

_Unwind_Ptr  __attribute__ ((noinline))
__getSP (void)
{
  return (_Unwind_Ptr) __builtin_dwarf_cfa() + SFRAME_SP_VAL_OFFSET;
}

libc_hidden_def (__getSP);