diff options
Diffstat (limited to 'sysdeps/generic/sframe.c')
-rw-r--r-- | sysdeps/generic/sframe.c | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/sysdeps/generic/sframe.c b/sysdeps/generic/sframe.c new file mode 100644 index 0000000..ba0830d --- /dev/null +++ b/sysdeps/generic/sframe.c @@ -0,0 +1,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); |