| /* sframe.c - SFrame decoder/encoder. |
| |
| Copyright (C) 2022-2024 Free Software Foundation, Inc. |
| |
| This file is part of libsframe. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program 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 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 "config.h" |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include "sframe-impl.h" |
| #include "swap.h" |
| |
| struct sf_fde_tbl |
| { |
| unsigned int count; |
| unsigned int alloced; |
| sframe_func_desc_entry entry[1]; |
| }; |
| |
| struct sf_fre_tbl |
| { |
| unsigned int count; |
| unsigned int alloced; |
| sframe_frame_row_entry entry[1]; |
| }; |
| |
| #define _sf_printflike_(string_index,first_to_check) \ |
| __attribute__ ((__format__ (__printf__, (string_index), (first_to_check)))) |
| |
| static void debug_printf (const char *, ...); |
| |
| static int _sframe_debug; /* Control for printing out debug info. */ |
| static int number_of_entries = 64; |
| |
| static void |
| sframe_init_debug (void) |
| { |
| static int inited; |
| |
| if (!inited) |
| { |
| _sframe_debug = getenv ("SFRAME_DEBUG") != NULL; |
| inited = 1; |
| } |
| } |
| |
| _sf_printflike_ (1, 2) |
| static void debug_printf (const char *format, ...) |
| { |
| if (_sframe_debug) |
| { |
| va_list args; |
| |
| va_start (args, format); |
| vfprintf (stderr, format, args); |
| va_end (args); |
| } |
| } |
| |
| /* Generate bitmask of given size in bytes. This is used for |
| some checks on the FRE start address. |
| SFRAME_FRE_TYPE_ADDR1 => 1 byte => [ bitmask = 0xff ] |
| SFRAME_FRE_TYPE_ADDR2 => 2 byte => [ bitmask = 0xffff ] |
| SFRAME_FRE_TYPE_ADDR4 => 4 byte => [ bitmask = 0xffffffff ]. */ |
| #define SFRAME_BITMASK_OF_SIZE(size_in_bytes) \ |
| (((uint64_t)1 << (size_in_bytes*8)) - 1) |
| |
| /* Store the specified error code into errp if it is non-NULL. |
| Return SFRAME_ERR. */ |
| |
| static int |
| sframe_set_errno (int *errp, int error) |
| { |
| if (errp != NULL) |
| *errp = error; |
| return SFRAME_ERR; |
| } |
| |
| /* Store the specified error code into errp if it is non-NULL. |
| Return NULL. */ |
| |
| static void * |
| sframe_ret_set_errno (int *errp, int error) |
| { |
| if (errp != NULL) |
| *errp = error; |
| return NULL; |
| } |
| |
| /* Get the SFrame header size. */ |
| |
| static uint32_t |
| sframe_get_hdr_size (sframe_header *sfh) |
| { |
| return SFRAME_V1_HDR_SIZE (*sfh); |
| } |
| |
| /* Access functions for frame row entry data. */ |
| |
| static uint8_t |
| sframe_fre_get_offset_count (uint8_t fre_info) |
| { |
| return SFRAME_V1_FRE_OFFSET_COUNT (fre_info); |
| } |
| |
| static uint8_t |
| sframe_fre_get_offset_size (uint8_t fre_info) |
| { |
| return SFRAME_V1_FRE_OFFSET_SIZE (fre_info); |
| } |
| |
| static bool |
| sframe_get_fre_ra_mangled_p (uint8_t fre_info) |
| { |
| return SFRAME_V1_FRE_MANGLED_RA_P (fre_info); |
| } |
| |
| /* Access functions for info from function descriptor entry. */ |
| |
| static uint32_t |
| sframe_get_fre_type (sframe_func_desc_entry *fdep) |
| { |
| uint32_t fre_type = 0; |
| if (fdep) |
| fre_type = SFRAME_V1_FUNC_FRE_TYPE (fdep->sfde_func_info); |
| return fre_type; |
| } |
| |
| static uint32_t |
| sframe_get_fde_type (sframe_func_desc_entry *fdep) |
| { |
| uint32_t fde_type = 0; |
| if (fdep) |
| fde_type = SFRAME_V1_FUNC_FDE_TYPE (fdep->sfde_func_info); |
| return fde_type; |
| } |
| |
| /* Check if flipping is needed, based on ENDIAN. */ |
| |
| static int |
| need_swapping (int endian) |
| { |
| unsigned int ui = 1; |
| char *c = (char *)&ui; |
| int is_little = (int)*c; |
| |
| switch (endian) |
| { |
| case SFRAME_ABI_AARCH64_ENDIAN_LITTLE: |
| case SFRAME_ABI_AMD64_ENDIAN_LITTLE: |
| return !is_little; |
| case SFRAME_ABI_AARCH64_ENDIAN_BIG: |
| return is_little; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| /* Flip the endianness of the SFrame header. */ |
| |
| static void |
| flip_header (sframe_header *sfheader) |
| { |
| swap_thing (sfheader->sfh_preamble.sfp_magic); |
| swap_thing (sfheader->sfh_preamble.sfp_version); |
| swap_thing (sfheader->sfh_preamble.sfp_flags); |
| swap_thing (sfheader->sfh_cfa_fixed_fp_offset); |
| swap_thing (sfheader->sfh_cfa_fixed_ra_offset); |
| swap_thing (sfheader->sfh_num_fdes); |
| swap_thing (sfheader->sfh_num_fres); |
| swap_thing (sfheader->sfh_fre_len); |
| swap_thing (sfheader->sfh_fdeoff); |
| swap_thing (sfheader->sfh_freoff); |
| } |
| |
| static void |
| flip_fde (sframe_func_desc_entry *fdep) |
| { |
| swap_thing (fdep->sfde_func_start_address); |
| swap_thing (fdep->sfde_func_size); |
| swap_thing (fdep->sfde_func_start_fre_off); |
| swap_thing (fdep->sfde_func_num_fres); |
| } |
| |
| /* Check if SFrame header has valid data. */ |
| |
| static bool |
| sframe_header_sanity_check_p (sframe_header *hp) |
| { |
| unsigned char all_flags = SFRAME_F_FDE_SORTED | SFRAME_F_FRAME_POINTER; |
| /* Check preamble is valid. */ |
| if (hp->sfh_preamble.sfp_magic != SFRAME_MAGIC |
| || (hp->sfh_preamble.sfp_version != SFRAME_VERSION_1 |
| && hp->sfh_preamble.sfp_version != SFRAME_VERSION_2) |
| || (hp->sfh_preamble.sfp_flags | all_flags) != all_flags) |
| return false; |
| |
| /* Check offsets are valid. */ |
| if (hp->sfh_fdeoff > hp->sfh_freoff) |
| return false; |
| |
| return true; |
| } |
| |
| /* Flip the start address pointed to by FP. */ |
| |
| static void |
| flip_fre_start_address (char *addr, uint32_t fre_type) |
| { |
| if (fre_type == SFRAME_FRE_TYPE_ADDR2) |
| { |
| uint16_t *start_addr = (uint16_t *)addr; |
| swap_thing (*start_addr); |
| } |
| else if (fre_type == SFRAME_FRE_TYPE_ADDR4) |
| { |
| uint32_t *start_addr = (uint32_t *)addr; |
| swap_thing (*start_addr); |
| } |
| } |
| |
| static void |
| flip_fre_stack_offsets (char *offsets, uint8_t offset_size, uint8_t offset_cnt) |
| { |
| int j; |
| |
| if (offset_size == SFRAME_FRE_OFFSET_2B) |
| { |
| uint16_t *ust = (uint16_t *)offsets; |
| for (j = offset_cnt; j > 0; ust++, j--) |
| swap_thing (*ust); |
| } |
| else if (offset_size == SFRAME_FRE_OFFSET_4B) |
| { |
| uint32_t *uit = (uint32_t *)offsets; |
| for (j = offset_cnt; j > 0; uit++, j--) |
| swap_thing (*uit); |
| } |
| } |
| |
| /* Get the FRE start address size, given the FRE_TYPE. */ |
| |
| static size_t |
| sframe_fre_start_addr_size (uint32_t fre_type) |
| { |
| size_t addr_size = 0; |
| switch (fre_type) |
| { |
| case SFRAME_FRE_TYPE_ADDR1: |
| addr_size = 1; |
| break; |
| case SFRAME_FRE_TYPE_ADDR2: |
| addr_size = 2; |
| break; |
| case SFRAME_FRE_TYPE_ADDR4: |
| addr_size = 4; |
| break; |
| default: |
| /* No other value is expected. */ |
| sframe_assert (0); |
| break; |
| } |
| return addr_size; |
| } |
| |
| /* Check if the FREP has valid data. */ |
| |
| static bool |
| sframe_fre_sanity_check_p (sframe_frame_row_entry *frep) |
| { |
| uint8_t offset_size, offset_cnt; |
| uint8_t fre_info; |
| |
| if (frep == NULL) |
| return false; |
| |
| fre_info = frep->fre_info; |
| offset_size = sframe_fre_get_offset_size (fre_info); |
| |
| if (offset_size != SFRAME_FRE_OFFSET_1B |
| && offset_size != SFRAME_FRE_OFFSET_2B |
| && offset_size != SFRAME_FRE_OFFSET_4B) |
| return false; |
| |
| offset_cnt = sframe_fre_get_offset_count (fre_info); |
| if (offset_cnt > MAX_NUM_STACK_OFFSETS) |
| return false; |
| |
| return true; |
| } |
| |
| /* Get FRE_INFO's offset size in bytes. */ |
| |
| static size_t |
| sframe_fre_offset_bytes_size (uint8_t fre_info) |
| { |
| uint8_t offset_size, offset_cnt; |
| |
| offset_size = sframe_fre_get_offset_size (fre_info); |
| |
| debug_printf ("offset_size = %u\n", offset_size); |
| |
| offset_cnt = sframe_fre_get_offset_count (fre_info); |
| |
| if (offset_size == SFRAME_FRE_OFFSET_2B |
| || offset_size == SFRAME_FRE_OFFSET_4B) /* 2 or 4 bytes. */ |
| return (offset_cnt * (offset_size * 2)); |
| |
| return (offset_cnt); |
| } |
| |
| /* Get total size in bytes to represent FREP in the binary format. This |
| includes the starting address, FRE info, and all the offsets. */ |
| |
| static size_t |
| sframe_fre_entry_size (sframe_frame_row_entry *frep, uint32_t fre_type) |
| { |
| if (frep == NULL) |
| return 0; |
| |
| uint8_t fre_info = frep->fre_info; |
| size_t addr_size = sframe_fre_start_addr_size (fre_type); |
| |
| return (addr_size + sizeof (frep->fre_info) |
| + sframe_fre_offset_bytes_size (fre_info)); |
| } |
| |
| /* Get the function descriptor entry at index FUNC_IDX in the decoder |
| context CTX. */ |
| |
| static sframe_func_desc_entry * |
| sframe_decoder_get_funcdesc_at_index (sframe_decoder_ctx *ctx, |
| uint32_t func_idx) |
| { |
| sframe_func_desc_entry *fdep; |
| uint32_t num_fdes; |
| int err; |
| |
| num_fdes = sframe_decoder_get_num_fidx (ctx); |
| if (num_fdes == 0 |
| || func_idx >= num_fdes |
| || ctx->sfd_funcdesc == NULL) |
| return sframe_ret_set_errno (&err, SFRAME_ERR_DCTX_INVAL); |
| |
| fdep = &ctx->sfd_funcdesc[func_idx]; |
| return fdep; |
| } |
| |
| /* Check whether for the given FDEP, the SFrame Frame Row Entry identified via |
| the START_IP_OFFSET and the END_IP_OFFSET, provides the stack trace |
| information for the PC. */ |
| |
| static bool |
| sframe_fre_check_range_p (sframe_func_desc_entry *fdep, |
| int32_t start_ip_offset, int32_t end_ip_offset, |
| int32_t pc) |
| { |
| int32_t start_ip, end_ip; |
| int32_t func_start_addr; |
| uint8_t rep_block_size; |
| uint32_t fde_type; |
| int32_t masked_pc; |
| bool mask_p; |
| bool ret; |
| |
| ret = false; |
| |
| if (!fdep) |
| return ret; |
| |
| func_start_addr = fdep->sfde_func_start_address; |
| fde_type = sframe_get_fde_type (fdep); |
| mask_p = (fde_type == SFRAME_FDE_TYPE_PCMASK); |
| rep_block_size = fdep->sfde_func_rep_size; |
| |
| if (!mask_p) |
| { |
| start_ip = start_ip_offset + func_start_addr; |
| end_ip = end_ip_offset + func_start_addr; |
| ret = ((start_ip <= pc) && (end_ip >= pc)); |
| } |
| else |
| { |
| /* For FDEs for repetitive pattern of insns, we need to return the FRE |
| where pc % rep_block_size is between start_ip_offset and |
| end_ip_offset. */ |
| masked_pc = pc % rep_block_size; |
| ret = ((start_ip_offset <= masked_pc) && (end_ip_offset >= masked_pc)); |
| } |
| |
| return ret; |
| } |
| |
| static int |
| flip_fre (char *fp, uint32_t fre_type, size_t *fre_size) |
| { |
| uint8_t fre_info; |
| uint8_t offset_size, offset_cnt; |
| size_t addr_size, fre_info_size = 0; |
| int err = 0; |
| |
| if (fre_size == NULL) |
| return sframe_set_errno (&err, SFRAME_ERR_INVAL); |
| |
| flip_fre_start_address (fp, fre_type); |
| |
| /* Advance the buffer pointer to where the FRE info is. */ |
| addr_size = sframe_fre_start_addr_size (fre_type); |
| fp += addr_size; |
| |
| /* FRE info is uint8_t. No need to flip. */ |
| fre_info = *(uint8_t*)fp; |
| offset_size = sframe_fre_get_offset_size (fre_info); |
| offset_cnt = sframe_fre_get_offset_count (fre_info); |
| |
| /* Advance the buffer pointer to where the stack offsets are. */ |
| fre_info_size = sizeof (uint8_t); |
| fp += fre_info_size; |
| flip_fre_stack_offsets (fp, offset_size, offset_cnt); |
| |
| *fre_size |
| = addr_size + fre_info_size + sframe_fre_offset_bytes_size (fre_info); |
| |
| return 0; |
| } |
| |
| /* Endian flip the contents of FRAME_BUF of size BUF_SIZE. |
| The SFrame header in the FRAME_BUF must be endian flipped prior to |
| calling flip_sframe. |
| |
| Endian flipping at decode time vs encode time have different needs. At |
| encode time, the frame_buf is in host endianness, and hence, values should |
| be read up before the buffer is changed to foreign endianness. This change |
| of behaviour is specified via TO_FOREIGN arg. |
| |
| If an error code is returned, the buffer should not be used. */ |
| |
| static int |
| flip_sframe (char *frame_buf, size_t buf_size, uint32_t to_foreign) |
| { |
| unsigned int i, j, prev_frep_index; |
| sframe_header *ihp; |
| char *fdes; |
| char *fp = NULL; |
| sframe_func_desc_entry *fdep; |
| unsigned int num_fdes = 0; |
| unsigned int num_fres = 0; |
| uint32_t fre_type = 0; |
| uint32_t fre_offset = 0; |
| size_t esz = 0; |
| size_t hdrsz = 0; |
| int err = 0; |
| /* For error checking. */ |
| size_t bytes_flipped = 0; |
| |
| /* Header must be in host endianness at this time. */ |
| ihp = (sframe_header *)frame_buf; |
| |
| if (!sframe_header_sanity_check_p (ihp)) |
| return sframe_set_errno (&err, SFRAME_ERR_BUF_INVAL); |
| |
| /* The contents of the SFrame header are safe to read. Get the number of |
| FDEs and the first FDE in the buffer. */ |
| hdrsz = sframe_get_hdr_size (ihp); |
| num_fdes = ihp->sfh_num_fdes; |
| fdes = frame_buf + hdrsz + ihp->sfh_fdeoff; |
| fdep = (sframe_func_desc_entry *)fdes; |
| |
| j = 0; |
| prev_frep_index = 0; |
| for (i = 0; i < num_fdes; fdep++, i++) |
| { |
| if ((char*)fdep >= (frame_buf + buf_size)) |
| goto bad; |
| |
| if (to_foreign) |
| { |
| num_fres = fdep->sfde_func_num_fres; |
| fre_type = sframe_get_fre_type (fdep); |
| fre_offset = fdep->sfde_func_start_fre_off; |
| } |
| |
| flip_fde (fdep); |
| bytes_flipped += sizeof (sframe_func_desc_entry); |
| |
| if (!to_foreign) |
| { |
| num_fres = fdep->sfde_func_num_fres; |
| fre_type = sframe_get_fre_type (fdep); |
| fre_offset = fdep->sfde_func_start_fre_off; |
| } |
| |
| fp = frame_buf + sframe_get_hdr_size (ihp) + ihp->sfh_freoff; |
| fp += fre_offset; |
| for (; j < prev_frep_index + num_fres; j++) |
| { |
| if (flip_fre (fp, fre_type, &esz)) |
| goto bad; |
| bytes_flipped += esz; |
| |
| if (esz == 0 || esz > buf_size) |
| goto bad; |
| fp += esz; |
| } |
| prev_frep_index = j; |
| } |
| /* All FDEs and FREs must have been endian flipped by now. */ |
| if ((j != ihp->sfh_num_fres) || (bytes_flipped != (buf_size - hdrsz))) |
| goto bad; |
| |
| /* Success. */ |
| return 0; |
| bad: |
| return SFRAME_ERR; |
| } |
| |
| /* The SFrame Decoder. */ |
| |
| /* Get SFrame header from the given decoder context DCTX. */ |
| |
| static sframe_header * |
| sframe_decoder_get_header (sframe_decoder_ctx *dctx) |
| { |
| sframe_header *hp = NULL; |
| if (dctx != NULL) |
| hp = &dctx->sfd_header; |
| return hp; |
| } |
| |
| /* Compare function for qsort'ing the FDE table. */ |
| |
| static int |
| fde_func (const void *p1, const void *p2) |
| { |
| const sframe_func_desc_entry *aa = p1; |
| const sframe_func_desc_entry *bb = p2; |
| |
| if (aa->sfde_func_start_address < bb->sfde_func_start_address) |
| return -1; |
| else if (aa->sfde_func_start_address > bb->sfde_func_start_address) |
| return 1; |
| return 0; |
| } |
| |
| /* Get IDX'th offset from FRE. Set errp as applicable. */ |
| |
| static int32_t |
| sframe_get_fre_offset (sframe_frame_row_entry *fre, int idx, int *errp) |
| { |
| uint8_t offset_cnt, offset_size; |
| |
| if (fre == NULL || !sframe_fre_sanity_check_p (fre)) |
| return sframe_set_errno (errp, SFRAME_ERR_FRE_INVAL); |
| |
| offset_cnt = sframe_fre_get_offset_count (fre->fre_info); |
| offset_size = sframe_fre_get_offset_size (fre->fre_info); |
| |
| if (offset_cnt < idx + 1) |
| return sframe_set_errno (errp, SFRAME_ERR_FREOFFSET_NOPRESENT); |
| |
| if (errp) |
| *errp = 0; /* Offset Valid. */ |
| |
| if (offset_size == SFRAME_FRE_OFFSET_1B) |
| { |
| int8_t *sp = (int8_t *)fre->fre_offsets; |
| return sp[idx]; |
| } |
| else if (offset_size == SFRAME_FRE_OFFSET_2B) |
| { |
| int16_t *sp = (int16_t *)fre->fre_offsets; |
| return sp[idx]; |
| } |
| else |
| { |
| int32_t *ip = (int32_t *)fre->fre_offsets; |
| return ip[idx]; |
| } |
| } |
| |
| /* Free the decoder context. */ |
| |
| void |
| sframe_decoder_free (sframe_decoder_ctx **dctxp) |
| { |
| if (dctxp != NULL) |
| { |
| sframe_decoder_ctx *dctx = *dctxp; |
| if (dctx == NULL) |
| return; |
| |
| if (dctx->sfd_funcdesc != NULL) |
| { |
| free (dctx->sfd_funcdesc); |
| dctx->sfd_funcdesc = NULL; |
| } |
| if (dctx->sfd_fres != NULL) |
| { |
| free (dctx->sfd_fres); |
| dctx->sfd_fres = NULL; |
| } |
| if (dctx->sfd_buf != NULL) |
| { |
| free (dctx->sfd_buf); |
| dctx->sfd_buf = NULL; |
| } |
| |
| free (*dctxp); |
| *dctxp = NULL; |
| } |
| } |
| |
| /* Create an FDE function info byte given an FRE_TYPE and an FDE_TYPE. */ |
| /* FIXME API for linker. Revisit if its better placed somewhere else? */ |
| |
| unsigned char |
| sframe_fde_create_func_info (uint32_t fre_type, |
| uint32_t fde_type) |
| { |
| unsigned char func_info; |
| sframe_assert (fre_type == SFRAME_FRE_TYPE_ADDR1 |
| || fre_type == SFRAME_FRE_TYPE_ADDR2 |
| || fre_type == SFRAME_FRE_TYPE_ADDR4); |
| sframe_assert (fde_type == SFRAME_FDE_TYPE_PCINC |
| || fde_type == SFRAME_FDE_TYPE_PCMASK); |
| func_info = SFRAME_V1_FUNC_INFO (fde_type, fre_type); |
| return func_info; |
| } |
| |
| /* Get the FRE type given the function size. */ |
| /* FIXME API for linker. Revisit if its better placed somewhere else? */ |
| |
| uint32_t |
| sframe_calc_fre_type (size_t func_size) |
| { |
| uint32_t fre_type = 0; |
| if (func_size < SFRAME_FRE_TYPE_ADDR1_LIMIT) |
| fre_type = SFRAME_FRE_TYPE_ADDR1; |
| else if (func_size < SFRAME_FRE_TYPE_ADDR2_LIMIT) |
| fre_type = SFRAME_FRE_TYPE_ADDR2; |
| /* Adjust the check a bit so that it remains warning-free but meaningful |
| on 32-bit systems. */ |
| else if (func_size <= (size_t) (SFRAME_FRE_TYPE_ADDR4_LIMIT - 1)) |
| fre_type = SFRAME_FRE_TYPE_ADDR4; |
| return fre_type; |
| } |
| |
| /* Get the base reg id from the FRE info. Set errp if failure. */ |
| |
| uint8_t |
| sframe_fre_get_base_reg_id (sframe_frame_row_entry *fre, int *errp) |
| { |
| if (fre == NULL) |
| return sframe_set_errno (errp, SFRAME_ERR_FRE_INVAL); |
| |
| uint8_t fre_info = fre->fre_info; |
| return SFRAME_V1_FRE_CFA_BASE_REG_ID (fre_info); |
| } |
| |
| /* Get the CFA offset from the FRE. If the offset is invalid, sets errp. */ |
| |
| int32_t |
| sframe_fre_get_cfa_offset (sframe_decoder_ctx *dctx ATTRIBUTE_UNUSED, |
| sframe_frame_row_entry *fre, int *errp) |
| { |
| return sframe_get_fre_offset (fre, SFRAME_FRE_CFA_OFFSET_IDX, errp); |
| } |
| |
| /* Get the FP offset from the FRE. If the offset is invalid, sets errp. */ |
| |
| int32_t |
| sframe_fre_get_fp_offset (sframe_decoder_ctx *dctx, |
| sframe_frame_row_entry *fre, int *errp) |
| { |
| uint32_t fp_offset_idx = 0; |
| 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 (errp) |
| *errp = 0; |
| return fp_offset; |
| } |
| |
| /* In some ABIs, the stack offset to recover RA (using the CFA) from is |
| fixed (like AMD64). In such cases, the stack offset to recover FP will |
| appear at the second index. */ |
| fp_offset_idx = ((sframe_decoder_get_fixed_ra_offset (dctx) |
| != SFRAME_CFA_FIXED_RA_INVALID) |
| ? SFRAME_FRE_RA_OFFSET_IDX |
| : SFRAME_FRE_FP_OFFSET_IDX); |
| return sframe_get_fre_offset (fre, fp_offset_idx, errp); |
| } |
| |
| /* Get the RA offset from the FRE. If the offset is invalid, sets errp. */ |
| |
| int32_t |
| sframe_fre_get_ra_offset (sframe_decoder_ctx *dctx, |
| sframe_frame_row_entry *fre, int *errp) |
| { |
| 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 (errp) |
| *errp = 0; |
| return ra_offset; |
| } |
| |
| /* Otherwise, get the RA offset from the FRE. */ |
| return sframe_get_fre_offset (fre, SFRAME_FRE_RA_OFFSET_IDX, errp); |
| } |
| |
| /* Get whether the RA is mangled. */ |
| |
| bool |
| sframe_fre_get_ra_mangled_p (sframe_decoder_ctx *dctx ATTRIBUTE_UNUSED, |
| sframe_frame_row_entry *fre, int *errp) |
| { |
| if (fre == NULL || !sframe_fre_sanity_check_p (fre)) |
| return sframe_set_errno (errp, SFRAME_ERR_FRE_INVAL); |
| |
| return sframe_get_fre_ra_mangled_p (fre->fre_info); |
| } |
| |
| static int |
| sframe_frame_row_entry_copy (sframe_frame_row_entry *dst, |
| sframe_frame_row_entry *src) |
| { |
| int err = 0; |
| |
| if (dst == NULL || src == NULL) |
| return sframe_set_errno (&err, SFRAME_ERR_INVAL); |
| |
| memcpy (dst, src, sizeof (sframe_frame_row_entry)); |
| return 0; |
| } |
| |
| /* Decode the SFrame FRE start address offset value from FRE_BUF in on-disk |
| binary format, given the FRE_TYPE. Updates the FRE_START_ADDR. |
| |
| Returns 0 on success, SFRAME_ERR otherwise. */ |
| |
| static int |
| sframe_decode_fre_start_address (const char *fre_buf, |
| uint32_t *fre_start_addr, |
| uint32_t fre_type) |
| { |
| uint32_t saddr = 0; |
| int err = 0; |
| size_t addr_size = 0; |
| |
| addr_size = sframe_fre_start_addr_size (fre_type); |
| |
| if (fre_type == SFRAME_FRE_TYPE_ADDR1) |
| { |
| uint8_t *uc = (uint8_t *)fre_buf; |
| saddr = (uint32_t)*uc; |
| } |
| else if (fre_type == SFRAME_FRE_TYPE_ADDR2) |
| { |
| uint16_t *ust = (uint16_t *)fre_buf; |
| /* SFrame is an unaligned on-disk format. Using memcpy helps avoid the |
| use of undesirable unaligned loads. See PR libsframe/29856. */ |
| uint16_t tmp = 0; |
| memcpy (&tmp, ust, addr_size); |
| saddr = (uint32_t)tmp; |
| } |
| else if (fre_type == SFRAME_FRE_TYPE_ADDR4) |
| { |
| uint32_t *uit = (uint32_t *)fre_buf; |
| uint32_t tmp = 0; |
| memcpy (&tmp, uit, addr_size); |
| saddr = (uint32_t)tmp; |
| } |
| else |
| return sframe_set_errno (&err, SFRAME_ERR_INVAL); |
| |
| *fre_start_addr = saddr; |
| return 0; |
| } |
| |
| /* Decode a frame row entry FRE which starts at location FRE_BUF. The function |
| updates ESZ to the size of the FRE as stored in the binary format. |
| |
| This function works closely with the SFrame binary format. |
| |
| Returns SFRAME_ERR if failure. */ |
| |
| static int |
| sframe_decode_fre (const char *fre_buf, sframe_frame_row_entry *fre, |
| uint32_t fre_type, size_t *esz) |
| { |
| int err = 0; |
| const char *stack_offsets = NULL; |
| size_t stack_offsets_sz; |
| size_t addr_size; |
| size_t fre_size; |
| |
| if (fre_buf == NULL || fre == NULL || esz == NULL) |
| return sframe_set_errno (&err, SFRAME_ERR_INVAL); |
| |
| /* Copy over the FRE start address. */ |
| sframe_decode_fre_start_address (fre_buf, &fre->fre_start_addr, fre_type); |
| |
| addr_size = sframe_fre_start_addr_size (fre_type); |
| fre->fre_info = *(uint8_t *)(fre_buf + addr_size); |
| /* Sanity check as the API works closely with the binary format. */ |
| sframe_assert (sizeof (fre->fre_info) == sizeof (uint8_t)); |
| |
| /* Cleanup the space for fre_offsets first, then copy over the valid |
| bytes. */ |
| memset (fre->fre_offsets, 0, MAX_OFFSET_BYTES); |
| /* Get offsets size. */ |
| stack_offsets_sz = sframe_fre_offset_bytes_size (fre->fre_info); |
| stack_offsets = fre_buf + addr_size + sizeof (fre->fre_info); |
| memcpy (fre->fre_offsets, stack_offsets, stack_offsets_sz); |
| |
| /* The FRE has been decoded. Use it to perform one last sanity check. */ |
| fre_size = sframe_fre_entry_size (fre, fre_type); |
| sframe_assert (fre_size == (addr_size + sizeof (fre->fre_info) |
| + stack_offsets_sz)); |
| *esz = fre_size; |
| |
| return 0; |
| } |
| |
| /* Decode the specified SFrame buffer CF_BUF of size CF_SIZE and return the |
| new SFrame decoder context. |
| |
| Sets ERRP for the caller if any error. Frees up the allocated memory in |
| case of error. */ |
| |
| sframe_decoder_ctx * |
| sframe_decode (const char *sf_buf, size_t sf_size, int *errp) |
| { |
| const sframe_preamble *sfp; |
| size_t hdrsz; |
| sframe_header *sfheaderp; |
| sframe_decoder_ctx *dctx; |
| char *frame_buf; |
| char *tempbuf = NULL; |
| |
| int fidx_size; |
| uint32_t fre_bytes; |
| int foreign_endian = 0; |
| |
| sframe_init_debug (); |
| |
| if ((sf_buf == NULL) || (!sf_size)) |
| return sframe_ret_set_errno (errp, SFRAME_ERR_INVAL); |
| else if (sf_size < sizeof (sframe_header)) |
| return sframe_ret_set_errno (errp, SFRAME_ERR_BUF_INVAL); |
| |
| sfp = (const sframe_preamble *) sf_buf; |
| |
| debug_printf ("sframe_decode: magic=0x%x version=%u flags=%u\n", |
| sfp->sfp_magic, sfp->sfp_version, sfp->sfp_flags); |
| |
| /* Check for foreign endianness. */ |
| if (sfp->sfp_magic != SFRAME_MAGIC) |
| { |
| if (sfp->sfp_magic == bswap_16 (SFRAME_MAGIC)) |
| foreign_endian = 1; |
| else |
| return sframe_ret_set_errno (errp, SFRAME_ERR_BUF_INVAL); |
| } |
| |
| /* Initialize a new decoder context. */ |
| if ((dctx = malloc (sizeof (sframe_decoder_ctx))) == NULL) |
| return sframe_ret_set_errno (errp, SFRAME_ERR_NOMEM); |
| memset (dctx, 0, sizeof (sframe_decoder_ctx)); |
| |
| if (foreign_endian) |
| { |
| /* Allocate a new buffer and initialize it. */ |
| tempbuf = (char *) malloc (sf_size * sizeof (char)); |
| if (tempbuf == NULL) |
| return sframe_ret_set_errno (errp, SFRAME_ERR_NOMEM); |
| memcpy (tempbuf, sf_buf, sf_size); |
| |
| /* Flip the header. */ |
| sframe_header *ihp = (sframe_header *) tempbuf; |
| flip_header (ihp); |
| /* Flip the rest of the SFrame section data buffer. */ |
| if (flip_sframe (tempbuf, sf_size, 0)) |
| { |
| free (tempbuf); |
| return sframe_ret_set_errno (errp, SFRAME_ERR_BUF_INVAL); |
| } |
| frame_buf = tempbuf; |
| /* This buffer is malloc'd when endian flipping the contents of the input |
| buffer are needed. Keep a reference to it so it can be free'd up |
| later in sframe_decoder_free (). */ |
| dctx->sfd_buf = tempbuf; |
| } |
| else |
| frame_buf = (char *)sf_buf; |
| |
| /* Handle the SFrame header. */ |
| dctx->sfd_header = *(sframe_header *) frame_buf; |
| /* Validate the contents of SFrame header. */ |
| sfheaderp = &dctx->sfd_header; |
| if (!sframe_header_sanity_check_p (sfheaderp)) |
| { |
| sframe_ret_set_errno (errp, SFRAME_ERR_NOMEM); |
| goto decode_fail_free; |
| } |
| hdrsz = sframe_get_hdr_size (sfheaderp); |
| frame_buf += hdrsz; |
| |
| /* Handle the SFrame Function Descriptor Entry section. */ |
| fidx_size |
| = sfheaderp->sfh_num_fdes * sizeof (sframe_func_desc_entry); |
| dctx->sfd_funcdesc = malloc (fidx_size); |
| if (dctx->sfd_funcdesc == NULL) |
| { |
| sframe_ret_set_errno (errp, SFRAME_ERR_NOMEM); |
| goto decode_fail_free; |
| } |
| memcpy (dctx->sfd_funcdesc, frame_buf, fidx_size); |
| |
| debug_printf ("%u total fidx size\n", fidx_size); |
| |
| frame_buf += (fidx_size); |
| |
| /* Handle the SFrame Frame Row Entry section. */ |
| dctx->sfd_fres = (char *) malloc (sfheaderp->sfh_fre_len); |
| if (dctx->sfd_fres == NULL) |
| { |
| sframe_ret_set_errno (errp, SFRAME_ERR_NOMEM); |
| goto decode_fail_free; |
| } |
| memcpy (dctx->sfd_fres, frame_buf, sfheaderp->sfh_fre_len); |
| |
| fre_bytes = sfheaderp->sfh_fre_len; |
| dctx->sfd_fre_nbytes = fre_bytes; |
| |
| debug_printf ("%u total fre bytes\n", fre_bytes); |
| |
| return dctx; |
| |
| decode_fail_free: |
| if (foreign_endian && tempbuf != NULL) |
| free (tempbuf); |
| sframe_decoder_free (&dctx); |
| dctx = NULL; |
| return dctx; |
| } |
| |
| /* Get the size of the SFrame header from the decoder context CTX. */ |
| |
| unsigned int |
| sframe_decoder_get_hdr_size (sframe_decoder_ctx *ctx) |
| { |
| sframe_header *dhp; |
| dhp = sframe_decoder_get_header (ctx); |
| return sframe_get_hdr_size (dhp); |
| } |
| |
| /* Get the SFrame's abi/arch info given the decoder context DCTX. */ |
| |
| uint8_t |
| sframe_decoder_get_abi_arch (sframe_decoder_ctx *dctx) |
| { |
| sframe_header *sframe_header; |
| sframe_header = sframe_decoder_get_header (dctx); |
| return sframe_header->sfh_abi_arch; |
| } |
| |
| /* Get the format version from the SFrame decoder context DCTX. */ |
| |
| uint8_t |
| sframe_decoder_get_version (sframe_decoder_ctx *dctx) |
| { |
| sframe_header *dhp; |
| dhp = sframe_decoder_get_header (dctx); |
| return dhp->sfh_preamble.sfp_version; |
| } |
| |
| /* Get the SFrame's fixed FP offset given the decoder context CTX. */ |
| int8_t |
| sframe_decoder_get_fixed_fp_offset (sframe_decoder_ctx *ctx) |
| { |
| sframe_header *dhp; |
| dhp = sframe_decoder_get_header (ctx); |
| return dhp->sfh_cfa_fixed_fp_offset; |
| } |
| |
| /* Get the SFrame's fixed RA offset given the decoder context CTX. */ |
| int8_t |
| sframe_decoder_get_fixed_ra_offset (sframe_decoder_ctx *ctx) |
| { |
| sframe_header *dhp; |
| dhp = sframe_decoder_get_header (ctx); |
| return dhp->sfh_cfa_fixed_ra_offset; |
| } |
| |
| /* Find the function descriptor entry which contains the specified address |
| ADDR. |
| This function is deprecated and will be removed from libsframe.so.2. */ |
| |
| void * |
| sframe_get_funcdesc_with_addr (sframe_decoder_ctx *ctx __attribute__ ((unused)), |
| int32_t addr __attribute__ ((unused)), |
| int *errp) |
| { |
| return sframe_ret_set_errno (errp, SFRAME_ERR_INVAL); |
| } |
| |
| /* Find the function descriptor entry starting which contains the specified |
| address ADDR. */ |
| |
| static sframe_func_desc_entry * |
| sframe_get_funcdesc_with_addr_internal (sframe_decoder_ctx *ctx, int32_t addr, |
| int *errp) |
| { |
| sframe_header *dhp; |
| sframe_func_desc_entry *fdp; |
| int low, high, cnt; |
| |
| if (ctx == NULL) |
| return sframe_ret_set_errno (errp, SFRAME_ERR_INVAL); |
| |
| dhp = sframe_decoder_get_header (ctx); |
| |
| if (dhp == NULL || dhp->sfh_num_fdes == 0 || ctx->sfd_funcdesc == NULL) |
| return sframe_ret_set_errno (errp, SFRAME_ERR_DCTX_INVAL); |
| /* If the FDE sub-section is not sorted on PCs, skip the lookup because |
| binary search cannot be used. */ |
| if ((dhp->sfh_preamble.sfp_flags & SFRAME_F_FDE_SORTED) == 0) |
| return sframe_ret_set_errno (errp, SFRAME_ERR_FDE_NOTSORTED); |
| |
| /* Do the binary search. */ |
| fdp = (sframe_func_desc_entry *) ctx->sfd_funcdesc; |
| low = 0; |
| high = dhp->sfh_num_fdes; |
| cnt = high; |
| while (low <= high) |
| { |
| int mid = low + (high - low) / 2; |
| |
| if (fdp[mid].sfde_func_start_address == addr) |
| return fdp + mid; |
| |
| if (fdp[mid].sfde_func_start_address < addr) |
| { |
| if (mid == (cnt - 1)) /* Check if it's the last one. */ |
| return fdp + (cnt - 1); |
| else if (fdp[mid+1].sfde_func_start_address > addr) |
| return fdp + mid; |
| low = mid + 1; |
| } |
| else |
| high = mid - 1; |
| } |
| |
| return sframe_ret_set_errno (errp, SFRAME_ERR_FDE_NOTFOUND); |
| } |
| |
| /* Get the end IP offset for the FRE at index i in the FDEP. The buffer FRES |
| is the starting location for the FRE. */ |
| |
| static uint32_t |
| sframe_fre_get_end_ip_offset (sframe_func_desc_entry *fdep, unsigned int i, |
| const char *fres) |
| { |
| uint32_t end_ip_offset; |
| uint32_t fre_type; |
| |
| fre_type = sframe_get_fre_type (fdep); |
| |
| /* Get the start address of the next FRE in sequence. */ |
| if (i < fdep->sfde_func_num_fres - 1) |
| { |
| sframe_decode_fre_start_address (fres, &end_ip_offset, fre_type); |
| end_ip_offset -= 1; |
| } |
| else |
| /* The end IP offset for the FRE needs to be deduced from the function |
| size. */ |
| end_ip_offset = fdep->sfde_func_size - 1; |
| |
| return end_ip_offset; |
| } |
| |
| /* Find the SFrame Row Entry which contains the PC. Returns |
| SFRAME_ERR if failure. */ |
| |
| int |
| sframe_find_fre (sframe_decoder_ctx *ctx, int32_t pc, |
| sframe_frame_row_entry *frep) |
| { |
| sframe_frame_row_entry cur_fre; |
| sframe_func_desc_entry *fdep; |
| uint32_t fre_type, fde_type, i; |
| int32_t start_ip_offset; |
| int32_t func_start_addr; |
| int32_t end_ip_offset; |
| const char *fres; |
| size_t size = 0; |
| int err = 0; |
| bool mask_p; |
| |
| if ((ctx == NULL) || (frep == NULL)) |
| return sframe_set_errno (&err, SFRAME_ERR_INVAL); |
| |
| /* Find the FDE which contains the PC, then scan its fre entries. */ |
| fdep = sframe_get_funcdesc_with_addr_internal (ctx, pc, &err); |
| if (fdep == NULL || ctx->sfd_fres == NULL) |
| return sframe_set_errno (&err, SFRAME_ERR_DCTX_INVAL); |
| |
| fre_type = sframe_get_fre_type (fdep); |
| fde_type = sframe_get_fde_type (fdep); |
| mask_p = (fde_type == SFRAME_FDE_TYPE_PCMASK); |
| |
| fres = ctx->sfd_fres + fdep->sfde_func_start_fre_off; |
| func_start_addr = fdep->sfde_func_start_address; |
| |
| for (i = 0; i < fdep->sfde_func_num_fres; i++) |
| { |
| err = sframe_decode_fre (fres, &cur_fre, fre_type, &size); |
| if (err) |
| return sframe_set_errno (&err, SFRAME_ERR_FRE_INVAL); |
| |
| start_ip_offset = cur_fre.fre_start_addr; |
| end_ip_offset = sframe_fre_get_end_ip_offset (fdep, i, fres + size); |
| |
| /* First FRE's start_ip must be more than pc for regular SFrame FDEs. */ |
| if (i == 0 && !mask_p && (start_ip_offset + func_start_addr) > pc) |
| return sframe_set_errno (&err, SFRAME_ERR_FRE_INVAL); |
| |
| if (sframe_fre_check_range_p (fdep, start_ip_offset, end_ip_offset, pc)) |
| { |
| sframe_frame_row_entry_copy (frep, &cur_fre); |
| return 0; |
| } |
| fres += size; |
| } |
| return sframe_set_errno (&err, SFRAME_ERR_FDE_INVAL); |
| } |
| |
| /* Return the number of function descriptor entries in the SFrame decoder |
| DCTX. */ |
| |
| uint32_t |
| sframe_decoder_get_num_fidx (sframe_decoder_ctx *ctx) |
| { |
| uint32_t num_fdes = 0; |
| sframe_header *dhp = NULL; |
| dhp = sframe_decoder_get_header (ctx); |
| if (dhp) |
| num_fdes = dhp->sfh_num_fdes; |
| return num_fdes; |
| } |
| |
| /* Get the data (NUM_FRES, FUNC_START_ADDRESS) from the function |
| descriptor entry at index I'th in the decoder CTX. If failed, |
| return error code. */ |
| /* FIXME - consolidate the args and return a |
| sframe_func_desc_index_elem rather? */ |
| |
| int |
| sframe_decoder_get_funcdesc (sframe_decoder_ctx *ctx, |
| unsigned int i, |
| uint32_t *num_fres, |
| uint32_t *func_size, |
| int32_t *func_start_address, |
| unsigned char *func_info) |
| { |
| sframe_func_desc_entry *fdp; |
| int err = 0; |
| |
| if (ctx == NULL || func_start_address == NULL || num_fres == NULL |
| || func_size == NULL) |
| return sframe_set_errno (&err, SFRAME_ERR_INVAL); |
| |
| fdp = sframe_decoder_get_funcdesc_at_index (ctx, i); |
| |
| if (fdp == NULL) |
| return sframe_set_errno (&err, SFRAME_ERR_FDE_NOTFOUND); |
| |
| *num_fres = fdp->sfde_func_num_fres; |
| *func_start_address = fdp->sfde_func_start_address; |
| *func_size = fdp->sfde_func_size; |
| *func_info = fdp->sfde_func_info; |
| |
| return 0; |
| } |
| |
| int |
| sframe_decoder_get_funcdesc_v2 (sframe_decoder_ctx *dctx, |
| unsigned int i, |
| uint32_t *num_fres, |
| uint32_t *func_size, |
| int32_t *func_start_address, |
| unsigned char *func_info, |
| uint8_t *rep_block_size) |
| { |
| sframe_func_desc_entry *fdp; |
| int err = 0; |
| |
| if (dctx == NULL || func_start_address == NULL |
| || num_fres == NULL || func_size == NULL |
| || sframe_decoder_get_version (dctx) == SFRAME_VERSION_1) |
| return sframe_set_errno (&err, SFRAME_ERR_INVAL); |
| |
| fdp = sframe_decoder_get_funcdesc_at_index (dctx, i); |
| |
| if (fdp == NULL) |
| return sframe_set_errno (&err, SFRAME_ERR_FDE_NOTFOUND); |
| |
| *num_fres = fdp->sfde_func_num_fres; |
| *func_start_address = fdp->sfde_func_start_address; |
| *func_size = fdp->sfde_func_size; |
| *func_info = fdp->sfde_func_info; |
| *rep_block_size = fdp->sfde_func_rep_size; |
| |
| return 0; |
| } |
| /* Get the FRE_IDX'th FRE of the function at FUNC_IDX'th function |
| descriptor entry in the SFrame decoder CTX. Returns error code as |
| applicable. */ |
| |
| int |
| sframe_decoder_get_fre (sframe_decoder_ctx *ctx, |
| unsigned int func_idx, |
| unsigned int fre_idx, |
| sframe_frame_row_entry *fre) |
| { |
| sframe_func_desc_entry *fdep; |
| sframe_frame_row_entry ifre; |
| const char *fres; |
| uint32_t i; |
| uint32_t fre_type; |
| size_t esz = 0; |
| int err = 0; |
| |
| if (ctx == NULL || fre == NULL) |
| return sframe_set_errno (&err, SFRAME_ERR_INVAL); |
| |
| /* Get function descriptor entry at index func_idx. */ |
| fdep = sframe_decoder_get_funcdesc_at_index (ctx, func_idx); |
| |
| if (fdep == NULL) |
| return sframe_set_errno (&err, SFRAME_ERR_FDE_NOTFOUND); |
| |
| fre_type = sframe_get_fre_type (fdep); |
| /* Now scan the FRE entries. */ |
| fres = ctx->sfd_fres + fdep->sfde_func_start_fre_off; |
| for (i = 0; i < fdep->sfde_func_num_fres; i++) |
| { |
| /* Decode the FRE at the current position. Return it if valid. */ |
| err = sframe_decode_fre (fres, &ifre, fre_type, &esz); |
| if (i == fre_idx) |
| { |
| if (!sframe_fre_sanity_check_p (&ifre)) |
| return sframe_set_errno (&err, SFRAME_ERR_FRE_INVAL); |
| |
| sframe_frame_row_entry_copy (fre, &ifre); |
| |
| if (fdep->sfde_func_size) |
| sframe_assert (fre->fre_start_addr < fdep->sfde_func_size); |
| else |
| /* A SFrame FDE with func size equal to zero is possible. */ |
| sframe_assert (fre->fre_start_addr == fdep->sfde_func_size); |
| |
| return 0; |
| } |
| /* Next FRE. */ |
| fres += esz; |
| } |
| |
| return sframe_set_errno (&err, SFRAME_ERR_FDE_NOTFOUND); |
| } |
| |
| |
| /* SFrame Encoder. */ |
| |
| /* Get a reference to the ENCODER's SFrame header. */ |
| |
| static sframe_header * |
| sframe_encoder_get_header (sframe_encoder_ctx *encoder) |
| { |
| sframe_header *hp = NULL; |
| if (encoder) |
| hp = &encoder->sfe_header; |
| return hp; |
| } |
| |
| static sframe_func_desc_entry * |
| sframe_encoder_get_funcdesc_at_index (sframe_encoder_ctx *encoder, |
| uint32_t func_idx) |
| { |
| sframe_func_desc_entry *fde = NULL; |
| if (func_idx < sframe_encoder_get_num_fidx (encoder)) |
| { |
| sf_fde_tbl *func_tbl = encoder->sfe_funcdesc; |
| fde = func_tbl->entry + func_idx; |
| } |
| return fde; |
| } |
| |
| /* Create an encoder context with the given SFrame format version VER, FLAGS |
| and ABI information. Uses the ABI specific FIXED_FP_OFFSET and |
| FIXED_RA_OFFSET values as provided. Sets errp if failure. */ |
| |
| sframe_encoder_ctx * |
| sframe_encode (uint8_t ver, uint8_t flags, uint8_t abi_arch, |
| int8_t fixed_fp_offset, int8_t fixed_ra_offset, int *errp) |
| { |
| sframe_header *hp; |
| sframe_encoder_ctx *encoder; |
| |
| if (ver != SFRAME_VERSION) |
| return sframe_ret_set_errno (errp, SFRAME_ERR_VERSION_INVAL); |
| |
| if ((encoder = malloc (sizeof (sframe_encoder_ctx))) == NULL) |
| return sframe_ret_set_errno (errp, SFRAME_ERR_NOMEM); |
| |
| memset (encoder, 0, sizeof (sframe_encoder_ctx)); |
| |
| /* Get the SFrame header and update it. */ |
| hp = sframe_encoder_get_header (encoder); |
| hp->sfh_preamble.sfp_version = ver; |
| hp->sfh_preamble.sfp_magic = SFRAME_MAGIC; |
| hp->sfh_preamble.sfp_flags = flags; |
| |
| hp->sfh_abi_arch = abi_arch; |
| hp->sfh_cfa_fixed_fp_offset = fixed_fp_offset; |
| hp->sfh_cfa_fixed_ra_offset = fixed_ra_offset; |
| |
| return encoder; |
| } |
| |
| /* Free the encoder context. */ |
| |
| void |
| sframe_encoder_free (sframe_encoder_ctx **encoder) |
| { |
| if (encoder != NULL) |
| { |
| sframe_encoder_ctx *ectx = *encoder; |
| if (ectx == NULL) |
| return; |
| |
| if (ectx->sfe_funcdesc != NULL) |
| { |
| free (ectx->sfe_funcdesc); |
| ectx->sfe_funcdesc = NULL; |
| } |
| if (ectx->sfe_fres != NULL) |
| { |
| free (ectx->sfe_fres); |
| ectx->sfe_fres = NULL; |
| } |
| if (ectx->sfe_data != NULL) |
| { |
| free (ectx->sfe_data); |
| ectx->sfe_data = NULL; |
| } |
| |
| free (*encoder); |
| *encoder = NULL; |
| } |
| } |
| |
| /* Get the size of the SFrame header from the encoder ctx ENCODER. */ |
| |
| unsigned int |
| sframe_encoder_get_hdr_size (sframe_encoder_ctx *encoder) |
| { |
| sframe_header *ehp; |
| ehp = sframe_encoder_get_header (encoder); |
| return sframe_get_hdr_size (ehp); |
| } |
| |
| /* Get the abi/arch info from the SFrame encoder context ENCODER. */ |
| |
| uint8_t |
| sframe_encoder_get_abi_arch (sframe_encoder_ctx *encoder) |
| { |
| uint8_t abi_arch = 0; |
| sframe_header *ehp; |
| ehp = sframe_encoder_get_header (encoder); |
| if (ehp) |
| abi_arch = ehp->sfh_abi_arch; |
| return abi_arch; |
| } |
| |
| /* Get the format version from the SFrame encoder context ENCODER. */ |
| |
| uint8_t |
| sframe_encoder_get_version (sframe_encoder_ctx *encoder) |
| { |
| sframe_header *ehp; |
| ehp = sframe_encoder_get_header (encoder); |
| return ehp->sfh_preamble.sfp_version; |
| } |
| |
| /* Return the number of function descriptor entries in the SFrame encoder |
| ENCODER. */ |
| |
| uint32_t |
| sframe_encoder_get_num_fidx (sframe_encoder_ctx *encoder) |
| { |
| uint32_t num_fdes = 0; |
| sframe_header *ehp = NULL; |
| ehp = sframe_encoder_get_header (encoder); |
| if (ehp) |
| num_fdes = ehp->sfh_num_fdes; |
| return num_fdes; |
| } |
| |
| /* Add an FRE to function at FUNC_IDX'th function descriptor entry in |
| the encoder context. */ |
| |
| int |
| sframe_encoder_add_fre (sframe_encoder_ctx *encoder, |
| unsigned int func_idx, |
| sframe_frame_row_entry *frep) |
| { |
| sframe_header *ehp; |
| sframe_func_desc_entry *fdep; |
| sframe_frame_row_entry *ectx_frep; |
| size_t offsets_sz, esz; |
| uint32_t fre_type; |
| size_t fre_tbl_sz; |
| int err = 0; |
| |
| if (encoder == NULL || frep == NULL) |
| return sframe_set_errno (&err, SFRAME_ERR_INVAL); |
| if (!sframe_fre_sanity_check_p (frep)) |
| return sframe_set_errno (&err, SFRAME_ERR_FRE_INVAL); |
| |
| /* Use func_idx to gather the function descriptor entry. */ |
| fdep = sframe_encoder_get_funcdesc_at_index (encoder, func_idx); |
| |
| if (fdep == NULL) |
| return sframe_set_errno (&err, SFRAME_ERR_FDE_NOTFOUND); |
| |
| fre_type = sframe_get_fre_type (fdep); |
| sf_fre_tbl *fre_tbl = encoder->sfe_fres; |
| |
| if (fre_tbl == NULL) |
| { |
| fre_tbl_sz = (sizeof (sf_fre_tbl) |
| + (number_of_entries * sizeof (sframe_frame_row_entry))); |
| fre_tbl = malloc (fre_tbl_sz); |
| |
| if (fre_tbl == NULL) |
| { |
| sframe_set_errno (&err, SFRAME_ERR_NOMEM); |
| goto bad; /* OOM. */ |
| } |
| memset (fre_tbl, 0, fre_tbl_sz); |
| fre_tbl->alloced = number_of_entries; |
| } |
| else if (fre_tbl->count == fre_tbl->alloced) |
| { |
| fre_tbl_sz = (sizeof (sf_fre_tbl) |
| + ((fre_tbl->alloced + number_of_entries) |
| * sizeof (sframe_frame_row_entry))); |
| fre_tbl = realloc (fre_tbl, fre_tbl_sz); |
| if (fre_tbl == NULL) |
| { |
| sframe_set_errno (&err, SFRAME_ERR_NOMEM); |
| goto bad; /* OOM. */ |
| } |
| |
| memset (&fre_tbl->entry[fre_tbl->alloced], 0, |
| number_of_entries * sizeof (sframe_frame_row_entry)); |
| fre_tbl->alloced += number_of_entries; |
| } |
| |
| ectx_frep = &fre_tbl->entry[fre_tbl->count]; |
| ectx_frep->fre_start_addr |
| = frep->fre_start_addr; |
| ectx_frep->fre_info = frep->fre_info; |
| |
| if (fdep->sfde_func_size) |
| sframe_assert (frep->fre_start_addr < fdep->sfde_func_size); |
| else |
| /* A SFrame FDE with func size equal to zero is possible. */ |
| sframe_assert (frep->fre_start_addr == fdep->sfde_func_size); |
| |
| /* frep has already been sanity check'd. Get offsets size. */ |
| offsets_sz = sframe_fre_offset_bytes_size (frep->fre_info); |
| memcpy (&ectx_frep->fre_offsets, &frep->fre_offsets, offsets_sz); |
| |
| esz = sframe_fre_entry_size (frep, fre_type); |
| fre_tbl->count++; |
| |
| encoder->sfe_fres = fre_tbl; |
| encoder->sfe_fre_nbytes += esz; |
| |
| ehp = sframe_encoder_get_header (encoder); |
| ehp->sfh_num_fres = fre_tbl->count; |
| |
| /* Update the value of the number of FREs for the function. */ |
| fdep->sfde_func_num_fres++; |
| |
| return 0; |
| |
| bad: |
| if (fre_tbl != NULL) |
| free (fre_tbl); |
| encoder->sfe_fres = NULL; |
| encoder->sfe_fre_nbytes = 0; |
| return -1; |
| } |
| |
| /* Add a new function descriptor entry with START_ADDR, FUNC_SIZE and NUM_FRES |
| to the encoder. */ |
| |
| int |
| sframe_encoder_add_funcdesc (sframe_encoder_ctx *encoder, |
| int32_t start_addr, |
| uint32_t func_size, |
| unsigned char func_info, |
| uint32_t num_fres __attribute__ ((unused))) |
| { |
| sframe_header *ehp; |
| sf_fde_tbl *fd_info; |
| size_t fd_tbl_sz; |
| int err = 0; |
| |
| /* FIXME book-keep num_fres for error checking. */ |
| if (encoder == NULL) |
| return sframe_set_errno (&err, SFRAME_ERR_INVAL); |
| |
| fd_info = encoder->sfe_funcdesc; |
| ehp = sframe_encoder_get_header (encoder); |
| |
| if (fd_info == NULL) |
| { |
| fd_tbl_sz = (sizeof (sf_fde_tbl) |
| + (number_of_entries * sizeof (sframe_func_desc_entry))); |
| fd_info = malloc (fd_tbl_sz); |
| if (fd_info == NULL) |
| { |
| sframe_set_errno (&err, SFRAME_ERR_NOMEM); |
| goto bad; /* OOM. */ |
| } |
| memset (fd_info, 0, fd_tbl_sz); |
| fd_info->alloced = number_of_entries; |
| } |
| else if (fd_info->count == fd_info->alloced) |
| { |
| fd_tbl_sz = (sizeof (sf_fde_tbl) |
| + ((fd_info->alloced + number_of_entries) |
| * sizeof (sframe_func_desc_entry))); |
| fd_info = realloc (fd_info, fd_tbl_sz); |
| if (fd_info == NULL) |
| { |
| sframe_set_errno (&err, SFRAME_ERR_NOMEM); |
| goto bad; /* OOM. */ |
| } |
| |
| memset (&fd_info->entry[fd_info->alloced], 0, |
| number_of_entries * sizeof (sframe_func_desc_entry)); |
| fd_info->alloced += number_of_entries; |
| } |
| |
| fd_info->entry[fd_info->count].sfde_func_start_address = start_addr; |
| /* Num FREs is updated as FREs are added for the function later via |
| sframe_encoder_add_fre. */ |
| fd_info->entry[fd_info->count].sfde_func_size = func_size; |
| fd_info->entry[fd_info->count].sfde_func_start_fre_off |
| = encoder->sfe_fre_nbytes; |
| #if 0 |
| // Linker optimization test code cleanup later ibhagat TODO FIXME |
| uint32_t fre_type = sframe_calc_fre_type (func_size); |
| |
| fd_info->entry[fd_info->count].sfde_func_info |
| = sframe_fde_func_info (fre_type); |
| #endif |
| fd_info->entry[fd_info->count].sfde_func_info = func_info; |
| fd_info->count++; |
| encoder->sfe_funcdesc = fd_info; |
| ehp->sfh_num_fdes++; |
| return 0; |
| |
| bad: |
| if (fd_info != NULL) |
| free (fd_info); |
| encoder->sfe_funcdesc = NULL; |
| ehp->sfh_num_fdes = 0; |
| return -1; |
| } |
| |
| /* Add a new function descriptor entry with START_ADDR, FUNC_SIZE, FUNC_INFO |
| and REP_BLOCK_SIZE to the encoder. |
| |
| This API is valid only for SFrame format version 2. */ |
| |
| int |
| sframe_encoder_add_funcdesc_v2 (sframe_encoder_ctx *encoder, |
| int32_t start_addr, |
| uint32_t func_size, |
| unsigned char func_info, |
| uint8_t rep_block_size, |
| uint32_t num_fres __attribute__ ((unused))) |
| { |
| sf_fde_tbl *fd_info; |
| int err; |
| |
| if (encoder == NULL |
| || sframe_encoder_get_version (encoder) == SFRAME_VERSION_1) |
| return sframe_set_errno (&err, SFRAME_ERR_INVAL); |
| |
| err = sframe_encoder_add_funcdesc (encoder, start_addr, func_size, func_info, |
| num_fres); |
| if (err) |
| return SFRAME_ERR; |
| |
| fd_info = encoder->sfe_funcdesc; |
| fd_info->entry[fd_info->count-1].sfde_func_rep_size = rep_block_size; |
| |
| return 0; |
| } |
| |
| static int |
| sframe_sort_funcdesc (sframe_encoder_ctx *encoder) |
| { |
| sframe_header *ehp; |
| |
| ehp = sframe_encoder_get_header (encoder); |
| /* Sort and write out the FDE table. */ |
| sf_fde_tbl *fd_info = encoder->sfe_funcdesc; |
| if (fd_info) |
| { |
| qsort (fd_info->entry, fd_info->count, |
| sizeof (sframe_func_desc_entry), fde_func); |
| /* Update preamble's flags. */ |
| ehp->sfh_preamble.sfp_flags |= SFRAME_F_FDE_SORTED; |
| } |
| return 0; |
| } |
| |
| /* Write the SFrame FRE start address from the in-memory FRE_START_ADDR |
| to the buffer CONTENTS (on-disk format), given the FRE_TYPE and |
| FRE_START_ADDR_SZ. */ |
| |
| static int |
| sframe_encoder_write_fre_start_addr (char *contents, |
| uint32_t fre_start_addr, |
| uint32_t fre_type, |
| size_t fre_start_addr_sz) |
| { |
| int err = 0; |
| |
| if (fre_type == SFRAME_FRE_TYPE_ADDR1) |
| { |
| uint8_t uc = fre_start_addr; |
| memcpy (contents, &uc, fre_start_addr_sz); |
| } |
| else if (fre_type == SFRAME_FRE_TYPE_ADDR2) |
| { |
| uint16_t ust = fre_start_addr; |
| memcpy (contents, &ust, fre_start_addr_sz); |
| } |
| else if (fre_type == SFRAME_FRE_TYPE_ADDR4) |
| { |
| uint32_t uit = fre_start_addr; |
| memcpy (contents, &uit, fre_start_addr_sz); |
| } |
| else |
| return sframe_set_errno (&err, SFRAME_ERR_INVAL); |
| |
| return 0; |
| } |
| |
| /* Write a frame row entry pointed to by FREP into the buffer CONTENTS. The |
| size in bytes written out are updated in ESZ. |
| |
| This function works closely with the SFrame binary format. |
| |
| Returns SFRAME_ERR if failure. */ |
| |
| static int |
| sframe_encoder_write_fre (char *contents, sframe_frame_row_entry *frep, |
| uint32_t fre_type, size_t *esz) |
| { |
| size_t fre_sz; |
| size_t fre_start_addr_sz; |
| size_t fre_stack_offsets_sz; |
| int err = 0; |
| |
| if (!sframe_fre_sanity_check_p (frep)) |
| return sframe_set_errno (&err, SFRAME_ERR_FRE_INVAL); |
| |
| fre_start_addr_sz = sframe_fre_start_addr_size (fre_type); |
| fre_stack_offsets_sz = sframe_fre_offset_bytes_size (frep->fre_info); |
| |
| /* The FRE start address must be encodable in the available number of |
| bytes. */ |
| uint64_t bitmask = SFRAME_BITMASK_OF_SIZE (fre_start_addr_sz); |
| sframe_assert ((uint64_t)frep->fre_start_addr <= bitmask); |
| |
| sframe_encoder_write_fre_start_addr (contents, frep->fre_start_addr, |
| fre_type, fre_start_addr_sz); |
| contents += fre_start_addr_sz; |
| |
| memcpy (contents, &frep->fre_info, sizeof (frep->fre_info)); |
| contents += sizeof (frep->fre_info); |
| |
| memcpy (contents, frep->fre_offsets, fre_stack_offsets_sz); |
| contents+= fre_stack_offsets_sz; |
| |
| fre_sz = sframe_fre_entry_size (frep, fre_type); |
| /* Sanity checking. */ |
| sframe_assert ((fre_start_addr_sz |
| + sizeof (frep->fre_info) |
| + fre_stack_offsets_sz) == fre_sz); |
| |
| *esz = fre_sz; |
| |
| return 0; |
| } |
| |
| /* Serialize the core contents of the SFrame section and write out to the |
| output buffer held in the ENCODER. Return SFRAME_ERR if failure. */ |
| |
| static int |
| sframe_encoder_write_sframe (sframe_encoder_ctx *encoder) |
| { |
| char *contents; |
| size_t buf_size; |
| size_t hdr_size; |
| size_t all_fdes_size; |
| size_t fre_size; |
| size_t esz = 0; |
| sframe_header *ehp; |
| unsigned char flags; |
| sf_fde_tbl *fd_info; |
| sf_fre_tbl *fr_info; |
| uint32_t i, num_fdes; |
| uint32_t j, num_fres; |
| sframe_func_desc_entry *fdep; |
| sframe_frame_row_entry *frep; |
| |
| uint32_t fre_type; |
| int err = 0; |
| |
| contents = encoder->sfe_data; |
| buf_size = encoder->sfe_data_size; |
| num_fdes = sframe_encoder_get_num_fidx (encoder); |
| all_fdes_size = num_fdes * sizeof (sframe_func_desc_entry); |
| ehp = sframe_encoder_get_header (encoder); |
| hdr_size = sframe_get_hdr_size (ehp); |
| |
| fd_info = encoder->sfe_funcdesc; |
| fr_info = encoder->sfe_fres; |
| |
| /* Sanity checks: |
| - buffers must be malloc'd by the caller. */ |
| if ((contents == NULL) || (buf_size < hdr_size)) |
| return sframe_set_errno (&err, SFRAME_ERR_BUF_INVAL); |
| if (fr_info == NULL) |
| return sframe_set_errno (&err, SFRAME_ERR_FRE_INVAL); |
| |
| /* Write out the FRE table first. |
| |
| Recall that read/write of FREs needs information from the corresponding |
| FDE; the latter stores the information about the FRE type record used for |
| the function. Also note that sorting of FDEs does NOT impact the order |
| in which FREs are stored in the SFrame's FRE sub-section. This means |
| that writing out FREs after sorting of FDEs will need some additional |
| book-keeping. At this time, we can afford to avoid it by writing out |
| the FREs first to the output buffer. */ |
| fre_size = 0; |
| uint32_t global = 0; |
| uint32_t fre_index = 0; |
| |
| contents += hdr_size + all_fdes_size; |
| for (i = 0; i < num_fdes; i++) |
| { |
| fdep = &fd_info->entry[i]; |
| fre_type = sframe_get_fre_type (fdep); |
| num_fres = fdep->sfde_func_num_fres; |
| |
| for (j = 0; j < num_fres; j++) |
| { |
| fre_index = global + j; |
| frep = &fr_info->entry[fre_index]; |
| |
| sframe_encoder_write_fre (contents, frep, fre_type, &esz); |
| contents += esz; |
| fre_size += esz; /* For debugging only. */ |
| } |
| global += j; |
| } |
| |
| sframe_assert (fre_size == ehp->sfh_fre_len); |
| sframe_assert (global == ehp->sfh_num_fres); |
| sframe_assert ((size_t)(contents - encoder->sfe_data) == buf_size); |
| |
| /* Sort the FDE table */ |
| sframe_sort_funcdesc (encoder); |
| |
| /* Sanity checks: |
| - the FDE section must have been sorted by now on the start address |
| of each function. */ |
| flags = ehp->sfh_preamble.sfp_flags; |
| if (!(flags & SFRAME_F_FDE_SORTED) |
| || (fd_info == NULL)) |
| return sframe_set_errno (&err, SFRAME_ERR_FDE_INVAL); |
| |
| contents = encoder->sfe_data; |
| /* Write out the SFrame header. The SFrame header in the encoder |
| object has already been updated with correct offsets by the caller. */ |
| memcpy (contents, ehp, hdr_size); |
| contents += hdr_size; |
| |
| /* Write out the FDE table sorted on funtion start address. */ |
| memcpy (contents, fd_info->entry, all_fdes_size); |
| contents += all_fdes_size; |
| |
| return 0; |
| } |
| |
| /* Serialize the contents of the encoder and return the buffer. ENCODED_SIZE |
| is updated to the size of the buffer. */ |
| |
| char * |
| sframe_encoder_write (sframe_encoder_ctx *encoder, |
| size_t *encoded_size, int *errp) |
| { |
| sframe_header *ehp; |
| size_t hdrsize, fsz, fresz, bufsize; |
| int foreign_endian; |
| |
| /* Initialize the encoded_size to zero. This makes it simpler to just |
| return from the function in case of failure. Free'ing up of |
| encoder->sfe_data is the responsibility of the caller. */ |
| *encoded_size = 0; |
| |
| if (encoder == NULL || encoded_size == NULL || errp == NULL) |
| return sframe_ret_set_errno (errp, SFRAME_ERR_INVAL); |
| |
| ehp = sframe_encoder_get_header (encoder); |
| hdrsize = sframe_get_hdr_size (ehp); |
| fsz = sframe_encoder_get_num_fidx (encoder) |
| * sizeof (sframe_func_desc_entry); |
| fresz = encoder->sfe_fre_nbytes; |
| |
| /* The total size of buffer is the sum of header, SFrame Function Descriptor |
| Entries section and the FRE section. */ |
| bufsize = hdrsize + fsz + fresz; |
| encoder->sfe_data = (char *) malloc (bufsize); |
| if (encoder->sfe_data == NULL) |
| return sframe_ret_set_errno (errp, SFRAME_ERR_NOMEM); |
| encoder->sfe_data_size = bufsize; |
| |
| /* Update the information in the SFrame header. */ |
| /* SFrame FDE section follows immediately after the header. */ |
| ehp->sfh_fdeoff = 0; |
| /* SFrame FRE section follows immediately after the SFrame FDE section. */ |
| ehp->sfh_freoff = fsz; |
| ehp->sfh_fre_len = fresz; |
| |
| foreign_endian = need_swapping (ehp->sfh_abi_arch); |
| |
| /* Write out the FDE Index and the FRE table in the sfe_data. */ |
| if (sframe_encoder_write_sframe (encoder)) |
| return sframe_ret_set_errno (errp, SFRAME_ERR_BUF_INVAL); |
| |
| /* Endian flip the contents if necessary. */ |
| if (foreign_endian) |
| { |
| if (flip_sframe (encoder->sfe_data, bufsize, 1)) |
| return sframe_ret_set_errno (errp, SFRAME_ERR_BUF_INVAL); |
| flip_header ((sframe_header*)encoder->sfe_data); |
| } |
| |
| *encoded_size = bufsize; |
| return encoder->sfe_data; |
| } |