linux-toolchains.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Indu Bhagat <indu.bhagat@oracle.com>
To: linux-toolchains@vger.kernel.org, rostedt@goodmis.org,
	peterz@infradead.org
Cc: Indu Bhagat <indu.bhagat@oracle.com>
Subject: [POC,V2 3/5] sframe: add new SFrame library
Date: Thu, 25 May 2023 22:32:13 -0700	[thread overview]
Message-ID: <20230526053215.3617580-4-indu.bhagat@oracle.com> (raw)
In-Reply-To: <20230526053215.3617580-1-indu.bhagat@oracle.com>

[Changes in V2]
  - Attempt to use better function names.
  - Use uint8_t, unit32_t, const char * consistently, where appropriate.
  - Revisit sframe_find_fre API.
  - Use kernel coding style - various fixes to code and comments.
  - Use consistent documentation style for all structures.
[End of Changes in V2]

This patch adds an implementation to read SFrame stack trace data from
a .sframe section.  Some APIs are also provided to find stack tracing
information per PC, e.g., given a PC, find the SFrame FRE.

These routines are provided in the sframe_read.h and sframe_read.c.

This implementation is malloc-free.

The usage of sframe_fre_copy () remains sframe_sec_find_fre ().  As
pointed out in V1 reviews, this call is not entirely necessary; I have
one more idea around optimizing this function, after which this call
will not be necessary in the current form.

Signed-off-by: Indu Bhagat <indu.bhagat@oracle.com>
---
 lib/Makefile             |   1 +
 lib/sframe/Makefile      |   5 +
 lib/sframe/sframe.h      | 264 +++++++++++++++++++
 lib/sframe/sframe_read.c | 549 +++++++++++++++++++++++++++++++++++++++
 lib/sframe/sframe_read.h |  80 ++++++
 5 files changed, 899 insertions(+)
 create mode 100644 lib/sframe/Makefile
 create mode 100644 lib/sframe/sframe.h
 create mode 100644 lib/sframe/sframe_read.c
 create mode 100644 lib/sframe/sframe_read.h

diff --git a/lib/Makefile b/lib/Makefile
index 876fcdeae34e..cb02d16dbffd 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -198,6 +198,7 @@ obj-$(CONFIG_ZSTD_COMPRESS) += zstd/
 obj-$(CONFIG_ZSTD_DECOMPRESS) += zstd/
 obj-$(CONFIG_XZ_DEC) += xz/
 obj-$(CONFIG_RAID6_PQ) += raid6/
+obj-$(CONFIG_USER_UNWINDER_SFRAME) += sframe/
 
 lib-$(CONFIG_DECOMPRESS_GZIP) += decompress_inflate.o
 lib-$(CONFIG_DECOMPRESS_BZIP2) += decompress_bunzip2.o
diff --git a/lib/sframe/Makefile b/lib/sframe/Makefile
new file mode 100644
index 000000000000..14d6cfd42a1d
--- /dev/null
+++ b/lib/sframe/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+##################################
+obj-$(CONFIG_USER_UNWINDER_SFRAME) += sframe_read.o
+
+CFLAGS_sframe_read.o += -I $(srctree)/lib/sframe/
diff --git a/lib/sframe/sframe.h b/lib/sframe/sframe.h
new file mode 100644
index 000000000000..340ec80ffa87
--- /dev/null
+++ b/lib/sframe/sframe.h
@@ -0,0 +1,264 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2023, Oracle and/or its affiliates.
+ */
+
+#ifndef SFRAME_H
+#define SFRAME_H
+
+#include <linux/types.h>
+
+/*
+ * This file contains definitions for the SFrame stack tracing format, which is
+ * documented at https://sourceware.org/binutils/docs
+ */
+
+#define SFRAME_VERSION_1	1
+#define SFRAME_MAGIC		0xdee2
+#define SFRAME_VERSION		SFRAME_VERSION_1
+
+/* Function Descriptor Entries are sorted on PC. */
+#define SFRAME_F_FDE_SORTED	0x1
+/* Frame-pointer based stack tracing. Defined, but not set. */
+#define SFRAME_F_FRAME_POINTER	0x2
+
+#define SFRAME_CFA_FIXED_FP_INVALID 0
+#define SFRAME_CFA_FIXED_RA_INVALID 0
+
+/* Supported ABIs/Arch. */
+#define SFRAME_ABI_AARCH64_ENDIAN_BIG	    1 /* AARCH64 big endian. */
+#define SFRAME_ABI_AARCH64_ENDIAN_LITTLE    2 /* AARCH64 little endian. */
+#define SFRAME_ABI_AMD64_ENDIAN_LITTLE	    3 /* AMD64 little endian. */
+
+/* SFrame FRE types. */
+#define SFRAME_FRE_TYPE_ADDR1	0
+#define SFRAME_FRE_TYPE_ADDR2	1
+#define SFRAME_FRE_TYPE_ADDR4	2
+
+/*
+ * SFrame Function Descriptor Entry types.
+ *
+ * The SFrame format has two possible representations for functions. The
+ * choice of which type to use is made according to the instruction patterns
+ * in the relevant program stub.
+ */
+
+/* Unwinders perform a (PC >= FRE_START_ADDR) to look up a matching FRE. */
+#define SFRAME_FDE_TYPE_PCINC	0
+/*
+ * Unwinders perform a (PC & FRE_START_ADDR_AS_MASK >= FRE_START_ADDR_AS_MASK)
+ * to look up a matching FRE. Typical usecases are pltN entries, trampolines
+ * etc.
+ */
+#define SFRAME_FDE_TYPE_PCMASK	1
+
+/**
+ * struct sframe_preamble - SFrame Preamble.
+ * @magic: Magic number (SFRAME_MAGIC).
+ * @version: Format version number (SFRAME_VERSION).
+ * @flags: Various flags.
+ */
+struct sframe_preamble {
+	uint16_t magic;
+	uint8_t version;
+	uint8_t flags;
+} __packed;
+
+/**
+ * struct sframe_header - SFrame Header.
+ * @preamble: SFrame preamble.
+ * @abi_arch: Identify the arch (including endianness) and ABI.
+ * @cfa_fixed_fp_offset: Offset for the Frame Pointer (FP) from CFA may be
+ *	  fixed  for some ABIs ((e.g, in AMD64 when -fno-omit-frame-pointer is
+ *	  used). When fixed, this field specifies the fixed stack frame offset
+ *	  and the individual FREs do not need to track it. When not fixed, it
+ *	  is set to SFRAME_CFA_FIXED_FP_INVALID, and the individual FREs may
+ *	  provide the applicable stack frame offset, if any.
+ * @cfa_fixed_ra_offset: Offset for the Return Address from CFA is fixed for
+ *	  some ABIs. When fixed, this field specifies the fixed stack frame
+ *	  offset and the individual FREs do not need to track it. When not
+ *	  fixed, it is set to SFRAME_CFA_FIXED_FP_INVALID.
+ * @auxhdr_len: Number of bytes making up the auxiliary header, if any.
+ *	  Some ABI/arch, in the future, may use this space for extending the
+ *	  information in SFrame header. Auxiliary header is contained in bytes
+ *	  sequentially following the sframe_header.
+ * @num_fdes: Number of SFrame FDEs in this SFrame section.
+ * @num_fres: Number of SFrame Frame Row Entries.
+ * @fre_len:  Number of bytes in the SFrame Frame Row Entry section.
+ * @fdes_off: Offset of SFrame Function Descriptor Entry section.
+ * @fres_off: Offset of SFrame Frame Row Entry section.
+ */
+struct sframe_header {
+	struct sframe_preamble preamble;
+	uint8_t abi_arch;
+	int8_t cfa_fixed_fp_offset;
+	int8_t cfa_fixed_ra_offset;
+	uint8_t auxhdr_len;
+	uint32_t num_fdes;
+	uint32_t num_fres;
+	uint32_t fre_len;
+	uint32_t fdes_off;
+	uint32_t fres_off;
+} __packed;
+
+#define SFRAME_V1_HDR_SIZE(sframe_hdr)	\
+	((sizeof(struct sframe_header) + (sframe_hdr).auxhdr_len))
+
+/* Two possible keys for executable (instruction) pointers signing. */
+#define SFRAME_AARCH64_PAUTH_KEY_A    0 /* Key A. */
+#define SFRAME_AARCH64_PAUTH_KEY_B    1 /* Key B. */
+
+/**
+ * struct sframe_func_desc_entry - SFrame Function Descriptor Entry.
+ * @func_start_addr: Function start address. Encoded as a signed offset,
+ *	  relative to the current FDE.
+ * @func_size: Size of the function in bytes.
+ * @func_fres_off: Offset of the first SFrame Frame Row Entry of the function,
+ *	  relative to the beginning of the SFrame Frame Row Entry sub-section.
+ * @func_fres_num: Number of frame row entries for the function.
+ * @func_info: Additional information for deciphering the stack trace
+ *	  information for the function. Contains information about SFrame FRE
+ *	  type, SFrame FDE type, PAC authorization A/B key, etc.
+ */
+struct sframe_func_desc_entry {
+	int32_t func_start_addr;
+	uint32_t func_size;
+	uint32_t func_fres_off;
+	uint32_t func_fres_num;
+	uint8_t func_info;
+} __packed;
+
+/*
+ * 'func_info' in SFrame FDE contains additional information for deciphering
+ * the stack trace information for the function. In V1, the information is
+ * organized as follows:
+ *   - 4-bits: Identify the FRE type used for the function.
+ *   - 1-bit: Identify the FDE type of the function - mask or inc.
+ *   - 1-bit: PAC authorization A/B key (aarch64).
+ *   - 2-bits: Unused.
+ * ---------------------------------------------------------------------
+ * |  Unused  |  PAC auth A/B key (aarch64) |  FDE type |   FRE type   |
+ * |          |        Unused (amd64)       |           |              |
+ * ---------------------------------------------------------------------
+ * 8          6                             5           4              0
+ */
+
+/* Note: Set PAC auth key to SFRAME_AARCH64_PAUTH_KEY_A by default.  */
+#define SFRAME_V1_FUNC_INFO(fde_type, fre_enc_type) \
+	(((SFRAME_AARCH64_PAUTH_KEY_A & 0x1) << 5) | \
+	 (((fde_type) & 0x1) << 4) | ((fre_enc_type) & 0xf))
+
+#define SFRAME_V1_FUNC_FRE_TYPE(data)	  ((data) & 0xf)
+#define SFRAME_V1_FUNC_FDE_TYPE(data)	  (((data) >> 4) & 0x1)
+#define SFRAME_V1_FUNC_PAUTH_KEY(data)	  (((data) >> 5) & 0x1)
+
+/*
+ * Size of stack frame offsets in an SFrame Frame Row Entry. A single
+ * SFrame FRE has all offsets of the same size. Offset size may vary
+ * across frame row entries.
+ */
+#define SFRAME_FRE_OFFSET_1B	  0
+#define SFRAME_FRE_OFFSET_2B	  1
+#define SFRAME_FRE_OFFSET_4B	  2
+
+/* An SFrame Frame Row Entry can be SP or FP based.  */
+#define SFRAME_BASE_REG_FP	0
+#define SFRAME_BASE_REG_SP	1
+
+/*
+ * The index at which a specific offset is presented in the variable length
+ * bytes of an FRE.
+ */
+#define SFRAME_FRE_CFA_OFFSET_IDX   0
+/*
+ * The RA stack offset, if present, will always be at index 1 in the variable
+ * length bytes of the FRE.
+ */
+#define SFRAME_FRE_RA_OFFSET_IDX    1
+/*
+ * The FP stack offset may appear at offset 1 or 2, depending on the ABI as RA
+ * may or may not be tracked.
+ */
+#define SFRAME_FRE_FP_OFFSET_IDX    2
+
+/**
+ * struct sframe_fre_info - SFrame FRE Info.
+ * @fre_info: Bitmap to store information to decipher the SFrame FREs, e.g.,
+ *	  number and size of stack offsets, whether the RA is mangled, etc.
+ */
+struct sframe_fre_info {
+	uint8_t fre_info;
+};
+
+/*
+ * 'fre_info' in SFrame FRE contains information about:
+ *   - 1 bit: base reg for CFA
+ *   - 4 bits: Number of offsets (N). A value of up to 3 is allowed to track
+ *   all three of CFA, FP and RA (fixed implicit order).
+ *   - 2 bits: information about size of the offsets (S) in bytes.
+ *     Valid values are SFRAME_FRE_OFFSET_1B, SFRAME_FRE_OFFSET_2B,
+ *     SFRAME_FRE_OFFSET_4B
+ *   - 1 bit: Mangled RA state bit (aarch64 only).
+ * ---------------------------------------------------------------
+ * | Mangled-RA (aarch64) |  Size of   |   Number of  | base_reg |
+ * |  Unused (amd64)      |  offsets   |    offsets   |          |
+ * ---------------------------------------------------------------
+ * 8                      7             5             1          0
+ */
+
+/* Note: Set mangled_ra_p to zero by default. */
+#define SFRAME_V1_FRE_INFO(base_reg_id, offset_num, offset_size) \
+	(((0 & 0x1) << 7) | (((offset_size) & 0x3) << 5) | \
+	 (((offset_num) & 0xf) << 1) | ((base_reg_id) & 0x1))
+
+/* Set the mangled_ra_p bit as indicated. */
+#define SFRAME_V1_FRE_INFO_UPDATE_MANGLED_RA_P(mangled_ra_p, fre_info) \
+	((((mangled_ra_p) & 0x1) << 7) | ((fre_info) & 0x7f))
+
+#define SFRAME_V1_FRE_CFA_BASE_REG_ID(data)	  ((data) & 0x1)
+#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)
+
+/* SFrame Frame Row Entry definitions. */
+
+/**
+ * struct sframe_frame_row_entry_addr1 - SFrame Frame Row Entry for 1-byte
+ *	  Address offsets.
+ * @fre_start_ip_off: Start address of the frame row entry. Encoded as a
+ *	  1-byte unsigned offset, relative to the start address of the
+ *	  function.
+ * @fre_info: SFrame FRE Info.
+ */
+struct sframe_frame_row_entry_addr1 {
+	uint8_t fre_start_ip_off;
+	struct sframe_fre_info fre_info;
+} __packed;
+
+/**
+ * struct sframe_frame_row_entry_addr2 - SFrame Frame Row Entry for 2-byte
+ *	  Address offsets.
+ * @fre_start_ip_off: Start address of the frame row entry. Encoded as a
+ *	  2-byte unsigned offset, relative to the start address of the
+ *	  function.
+ * @fre_info: SFrame FRE Info.
+ */
+struct sframe_frame_row_entry_addr2 {
+	uint16_t fre_start_ip_off;
+	struct sframe_fre_info fre_info;
+} __packed;
+
+/**
+ * struct sframe_frame_row_entry_addr4 - SFrame Frame Row Entry for 4-byte
+ *	  Address offsets.
+ * @fre_start_ip_off: Start address of the frame row entry. Encoded as a
+ *	  4-byte unsigned offset, relative to the start address of the
+ *	  function.
+ * @fre_info: SFrame FRE Info.
+ */
+struct sframe_frame_row_entry_addr4 {
+	uint32_t fre_start_ip_off;
+	struct sframe_fre_info fre_info;
+} __packed;
+
+#endif /* SFRAME_H */
diff --git a/lib/sframe/sframe_read.c b/lib/sframe/sframe_read.c
new file mode 100644
index 000000000000..ebb7d38ac6ef
--- /dev/null
+++ b/lib/sframe/sframe_read.c
@@ -0,0 +1,549 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2023, Oracle and/or its affiliates.
+ */
+
+#include <linux/string.h>
+
+#include "sframe_read.h"
+
+/**
+ * struct sframe_sec - SFrame section
+ * @header: SFrame header.
+ * @fdes: SFrame Function Descriptor Entries.
+ * @fres: SFrame Frame Row Entries.
+ * @fre_nbytes: Number of bytes needed for SFrame FREs.
+ */
+struct sframe_sec {
+	struct sframe_header header;
+	const char *fdes;
+	const char *fres;
+	uint32_t fre_nbytes;
+};
+
+static int sframe_set_errno(int *errp, int error)
+{
+	if (errp)
+		*errp = error;
+	return SFRAME_ERR;
+}
+
+static uint32_t sframe_sec_get_hdr_size(struct sframe_header *sfh)
+{
+	return SFRAME_V1_HDR_SIZE(*sfh);
+}
+
+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 uint32_t sframe_get_fre_type(struct sframe_func_desc_entry *fdep)
+{
+	if (!fdep)
+		return 0;
+
+	return SFRAME_V1_FUNC_FRE_TYPE(fdep->func_info);
+}
+
+static uint32_t sframe_get_fde_type(struct sframe_func_desc_entry *fdep)
+{
+	if (!fdep)
+		return 0;
+
+	return SFRAME_V1_FUNC_FDE_TYPE(fdep->func_info);
+}
+
+static bool sframe_header_sanity_check_p(struct sframe_header *hp)
+{
+	uint8_t all_flags;
+
+	all_flags = SFRAME_F_FDE_SORTED | SFRAME_F_FRAME_POINTER;
+
+	/* Check that the preamble is valid. */
+	if ((hp->preamble.magic != SFRAME_MAGIC) ||
+	    (hp->preamble.version != SFRAME_VERSION) ||
+	    ((hp->preamble.flags | all_flags) != all_flags))
+		return false;
+
+	/* Check that the offsets are valid. */
+	if (hp->fdes_off > hp->fres_off)
+		return false;
+
+	return true;
+}
+
+static bool sframe_fre_sanity_check_p(struct sframe_fre *frep)
+{
+	uint8_t offset_size;
+	uint8_t offset_cnt;
+
+	if (!frep)
+		return false;
+
+	offset_size = sframe_fre_get_offset_size(frep->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(frep->fre_info);
+	if (offset_cnt > MAX_NUM_STACK_OFFSETS)
+		return false;
+
+	return true;
+}
+
+static int32_t sframe_get_fre_offset(struct sframe_fre *frep, uint32_t idx,
+				     int *errp)
+{
+	uint8_t offset_size;
+	uint8_t offset_cnt;
+
+	if (!frep || !sframe_fre_sanity_check_p(frep))
+		return sframe_set_errno(errp, SFRAME_ERR_FRE_INVAL);
+
+	offset_cnt = sframe_fre_get_offset_count(frep->fre_info);
+	offset_size = sframe_fre_get_offset_size(frep->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 *stack_offsets = (int8_t *)frep->fre_offsets;
+		return stack_offsets[idx];
+	} else if (offset_size == SFRAME_FRE_OFFSET_2B) {
+		int16_t *stack_offsets = (int16_t *)frep->fre_offsets;
+		return stack_offsets[idx];
+	} else {
+		int32_t *stack_offsets = (int32_t *)frep->fre_offsets;
+		return stack_offsets[idx];
+	}
+}
+
+static struct sframe_header *sframe_sec_get_header(struct sframe_sec *sfsec)
+{
+	if (!sfsec)
+		return NULL;
+
+	return &sfsec->header;
+}
+
+static int sframe_fre_copy(struct sframe_fre *dst,
+			   struct sframe_fre *src)
+{
+	if (!dst || !src)
+		return SFRAME_ERR;
+
+	memcpy(dst, src, sizeof(struct sframe_fre));
+	return 0;
+}
+
+static int sframe_read_start_ip_offset(const char *fre_buf,
+				       uint32_t *start_ip_offset,
+				       uint32_t fre_type)
+{
+	uint32_t saddr = 0;
+	uint32_t *uit;
+	uint16_t *ust;
+	uint8_t *uc;
+
+	if (fre_type == SFRAME_FRE_TYPE_ADDR1) {
+		*uc = (uint8_t *)fre_buf;
+		saddr = (uint32_t)*uc;
+	} else if (fre_type == SFRAME_FRE_TYPE_ADDR2) {
+		*ust = (uint16_t *)fre_buf;
+		saddr = (uint32_t)*ust;
+	} else if (fre_type == SFRAME_FRE_TYPE_ADDR4) {
+		*uit = (uint32_t *)fre_buf;
+		saddr = (uint32_t)*uit;
+	} else {
+		return SFRAME_ERR_INVAL;
+	}
+
+	*start_ip_offset = saddr;
+	return 0;
+}
+
+/* Get the total size in bytes for the stack offsets. */
+static size_t sframe_fre_stack_offsets_sizeof(uint8_t fre_info)
+{
+	uint8_t offset_size;
+	uint8_t offset_cnt;
+
+	offset_size = sframe_fre_get_offset_size(fre_info);
+	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;
+}
+
+static size_t sframe_fre_ip_offset_sizeof(uint32_t fre_type)
+{
+	/* Type size of the start_addr in an FRE. */
+	size_t saddr_tsize = 0;
+
+	switch (fre_type) {
+	case SFRAME_FRE_TYPE_ADDR1:
+		saddr_tsize = sizeof(uint8_t);
+		break;
+	case SFRAME_FRE_TYPE_ADDR2:
+		saddr_tsize = sizeof(uint16_t);
+		break;
+	case SFRAME_FRE_TYPE_ADDR4:
+		saddr_tsize = sizeof(uint32_t);
+		break;
+	default:
+		/* No other value is expected. */
+		break;
+	}
+	return saddr_tsize;
+}
+
+static size_t sframe_fre_vlen_size(struct sframe_fre *frep,
+				   uint32_t fre_type)
+{
+	size_t ip_offset_size;
+	size_t fre_vlen_size;
+	uint8_t fre_info;
+
+	if (!frep)
+		return 0;
+
+	fre_info = frep->fre_info;
+	ip_offset_size = sframe_fre_ip_offset_sizeof(fre_type);
+
+	/*
+	 * An SFrame FRE is a variable length structure. It includes the start
+	 * IP offset, the FRE info field, and all trailing the stack offsets.
+	 */
+	fre_vlen_size = ip_offset_size + sizeof(fre_info);
+	fre_vlen_size += sframe_fre_stack_offsets_sizeof(fre_info);
+
+	return fre_vlen_size;
+}
+
+/*
+ * Get the end IP offset of the SFrame FRE at the given index.
+ * The end IP offset is not directly encoded in the FRE.
+ */
+static uint32_t
+sframe_fre_get_end_ip_offset(struct 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);
+
+	/*
+	 * Use the start address of the next FRE in sequence. If this if the
+	 * last FRE, end IP offset needs to be deduced from the function size.
+	 */
+	if (i < fdep->func_fres_num - 1) {
+		sframe_read_start_ip_offset(fres, &end_ip_offset, fre_type);
+		end_ip_offset -= 1;
+	} else {
+		end_ip_offset = fdep->func_size - 1;
+	}
+
+	return end_ip_offset;
+}
+
+/*
+ * Read an SFrame FRE which starts at location FRE_BUF. The function updates
+ * FRE_SIZE to the size of the FRE as stored in the binary format.
+ *
+ * Returns SFRAME_ERR if failure.
+ */
+static int sframe_sec_read_fre(const char *fre_buf, struct sframe_fre *frep,
+			       uint32_t fre_type, size_t *fre_size)
+{
+	const char *stack_offsets_buf;
+	size_t stack_offsets_size;
+	size_t ip_offset_size;
+	size_t size;
+
+	if (!fre_buf || !frep || !fre_size)
+		return SFRAME_ERR_INVAL;
+
+	/* Copy over the FRE start address. */
+	sframe_read_start_ip_offset(fre_buf, &frep->start_ip_offset, fre_type);
+
+	ip_offset_size = sframe_fre_ip_offset_sizeof(fre_type);
+	/* PS: Note how this API works closely with SFrame binary format. */
+	frep->fre_info = *(uint8_t *)(fre_buf + ip_offset_size);
+
+	memset(frep->fre_offsets, 0, MAX_STACK_OFFSET_NBYTES);
+	/* Copy over the stack offsets. */
+	stack_offsets_size = sframe_fre_stack_offsets_sizeof(frep->fre_info);
+	stack_offsets_buf = fre_buf + ip_offset_size + sizeof(frep->fre_info);
+	memcpy(frep->fre_offsets, stack_offsets_buf, stack_offsets_size);
+
+	size = sframe_fre_vlen_size(frep, fre_type);
+	*fre_size = size;
+
+	return 0;
+}
+
+static struct sframe_func_desc_entry *
+sframe_sec_find_fde(struct sframe_sec *sfsec, int32_t addr, int *errp)
+{
+	struct sframe_func_desc_entry *fde;
+	struct sframe_header *header;
+	int low;
+	int high;
+	int cnt;
+
+	if (!sfsec) {
+		sframe_set_errno(errp, SFRAME_ERR_INVAL);
+		return NULL;
+	}
+
+	header = sframe_sec_get_header(sfsec);
+	if (!header || (header->num_fdes == 0) || !sfsec->fdes) {
+		sframe_set_errno(errp, SFRAME_ERR_INIT_INVAL);
+		return NULL;
+	}
+	/*
+	 * Skip binary search if FDE sub-section is not sorted on PCs. GNU ld
+	 * sorts the FDEs on start PC by default though.
+	 */
+	if ((header->preamble.flags & SFRAME_F_FDE_SORTED) == 0) {
+		sframe_set_errno(errp, SFRAME_ERR_FDE_NOTSORTED);
+		return NULL;
+	}
+
+	/* Find the FDE that may contain the addr. */
+	fde = (struct sframe_func_desc_entry *)sfsec->fdes;
+	low = 0;
+	high = header->num_fdes;
+	cnt = high;
+	while (low <= high) {
+		int mid = low + (high - low) / 2;
+
+		if (fde[mid].func_start_addr == addr)
+			return fde + mid;
+
+		if (fde[mid].func_start_addr < addr) {
+			if (mid == (cnt - 1))
+				return fde + (cnt - 1);
+			else if (fde[mid+1].func_start_addr > addr)
+				return fde + mid;
+			low = mid + 1;
+		} else
+			high = mid - 1;
+	}
+
+	sframe_set_errno(errp, SFRAME_ERR_FDE_NOTFOUND);
+	return NULL;
+}
+
+static int8_t sframe_sec_get_fixed_fp_offset(struct sframe_sec *sfsec)
+{
+	struct sframe_header *header = sframe_sec_get_header(sfsec);
+
+	return header->cfa_fixed_fp_offset;
+}
+
+static int8_t sframe_sec_get_fixed_ra_offset(struct sframe_sec *sfsec)
+{
+	struct sframe_header *header = sframe_sec_get_header(sfsec);
+
+	return header->cfa_fixed_ra_offset;
+}
+
+size_t sframe_sec_sizeof(void)
+{
+	return sizeof(struct sframe_sec);
+}
+
+int sframe_sec_init(struct sframe_sec *sfsec, const char *sf_buf,
+		    size_t sf_size)
+{
+	const struct sframe_preamble *preamble;
+	struct sframe_header *header;
+	const char *frame_buf;
+
+	if (!sf_buf || (sf_size < sizeof(struct sframe_header)))
+		return SFRAME_ERR_INVAL;
+
+	/* Check for foreign endianness. */
+	preamble = (const struct sframe_preamble *) sf_buf;
+	if (preamble->magic != SFRAME_MAGIC)
+		return SFRAME_ERR_INVAL;
+
+	/* Reset the SFrame section object. */
+	memset(sfsec, 0, sizeof(struct sframe_sec));
+
+	frame_buf = (char *)sf_buf;
+
+	/* Initialize the reference to the SFrame header. */
+	sfsec->header = *(struct sframe_header *) frame_buf;
+	header = &sfsec->header;
+	if (!sframe_header_sanity_check_p(header))
+		return SFRAME_ERR_INVAL;
+
+	/* Initialize the reference to the SFrame FDE section. */
+	frame_buf += sframe_sec_get_hdr_size(header);
+	sfsec->fdes = frame_buf;
+
+	/* Initialize the reference to the SFrame FRE section. */
+	frame_buf += (header->num_fdes * sizeof(struct sframe_func_desc_entry));
+	sfsec->fres = frame_buf;
+
+	sfsec->fre_nbytes = header->fre_len;
+
+	return 0;
+}
+
+/*
+ * Find the SFrame Frame Row Entry which contains the PC.
+ * Returns error code if failure.
+ */
+int sframe_sec_find_fre(struct sframe_sec *sfsec, int32_t pc,
+			struct sframe_fre *frep)
+{
+	struct sframe_func_desc_entry *fdep;
+	struct sframe_fre cur_fre;
+	int32_t func_start_addr;
+	const char *fres_next;
+	uint32_t end_offset;
+	uint32_t fre_type;
+	uint32_t fde_type;
+	const char *fres;
+	int32_t start_ip;
+	size_t size = 0;
+	int32_t end_ip;
+	int err = 0;
+	uint32_t i;
+	/*
+	 * For regular FDEs(i.e. fde_type SFRAME_FDE_TYPE_PCINC),
+	 * where the start address in the FRE is an offset from start pc,
+	 * use a bitmask with all bits set so that none of the address bits are
+	 * ignored. In this case, we need to return the FRE where
+	 * (PC >= FRE_START_ADDR).
+	 */
+	uint64_t bitmask = 0xffffffff;
+
+	if (!sfsec || !frep)
+		return SFRAME_ERR_INVAL;
+
+	/* Find the FDE which contains the PC. */
+	fdep = sframe_sec_find_fde(sfsec, pc, &err);
+	if (!fdep || !sfsec->fres)
+		return SFRAME_ERR_INIT_INVAL;
+
+	fre_type = sframe_get_fre_type(fdep);
+	fde_type = sframe_get_fde_type(fdep);
+
+	/*
+	 * For FDEs for repetitive pattern of insns, we need to return the FRE
+	 * such that(PC & FRE_START_ADDR_AS_MASK >= FRE_START_ADDR_AS_MASK).
+	 * so, update the bitmask to the start address.
+	 */
+	/* FIXME - the bitmask should be encoded explicitly in the format. */
+	if (fde_type == SFRAME_FDE_TYPE_PCMASK)
+		bitmask = 0xff;
+
+	fres = sfsec->fres + fdep->func_fres_off;
+	func_start_addr = fdep->func_start_addr;
+
+	for (i = 0; i < fdep->func_fres_num; i++) {
+		err = sframe_sec_read_fre(fres, &cur_fre, fre_type, &size);
+		if (err)
+			return sframe_set_errno(&err, SFRAME_ERR_FRE_INVAL);
+
+		start_ip = func_start_addr + cur_fre.start_ip_offset;
+		fres_next = fres + size;
+		end_offset = sframe_fre_get_end_ip_offset(fdep, i, fres_next);
+		end_ip = func_start_addr + end_offset;
+
+		if ((start_ip & bitmask) > (pc & bitmask))
+			return sframe_set_errno(&err, SFRAME_ERR_FRE_INVAL);
+
+		if (((start_ip & bitmask) <= (pc & bitmask)) &&
+		    ((end_ip & bitmask) >= (pc & bitmask))) {
+			sframe_fre_copy(frep, &cur_fre);
+			return 0;
+		}
+		fres += size;
+	}
+	return sframe_set_errno(&err, SFRAME_ERR_FDE_INVAL);
+}
+
+unsigned int sframe_fre_get_base_reg_id(struct sframe_fre *frep,
+					int *errp)
+{
+	if (!frep)
+		return sframe_set_errno(errp, SFRAME_ERR_FRE_INVAL);
+
+	return SFRAME_V1_FRE_CFA_BASE_REG_ID(frep->fre_info);
+}
+
+int32_t sframe_fre_get_cfa_offset(struct sframe_sec *sfsec __always_unused,
+				  struct sframe_fre *frep, int *errp)
+{
+	return sframe_get_fre_offset(frep, SFRAME_FRE_CFA_OFFSET_IDX, errp);
+}
+
+int32_t sframe_fre_get_fp_offset(struct sframe_sec *sfsec,
+				 struct sframe_fre *frep, int *errp)
+{
+	uint32_t fp_offset_idx;
+	int8_t fp_offset;
+	int8_t ra_offset;
+
+	fp_offset = sframe_sec_get_fixed_fp_offset(sfsec);
+	fp_offset_idx = SFRAME_FRE_FP_OFFSET_IDX;
+	/*
+	 * 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) {
+		*errp = 0;
+		return fp_offset;
+	}
+
+	/*
+	 * In some ABIs, the stack offset to recover RA from (relative to the
+	 * CFA) is fixed (like AMD64). In such cases, the stack offset to
+	 * recover FP will appear at the second index.
+	 */
+	ra_offset = sframe_sec_get_fixed_ra_offset(sfsec);
+	if (ra_offset != SFRAME_CFA_FIXED_RA_INVALID)
+		fp_offset_idx = SFRAME_FRE_RA_OFFSET_IDX;
+
+	return sframe_get_fre_offset(frep, fp_offset_idx, errp);
+}
+
+int32_t sframe_fre_get_ra_offset(struct sframe_sec *sfsec,
+				 struct sframe_fre *frep, int *errp)
+{
+	int8_t ra_offset;
+
+	ra_offset = sframe_sec_get_fixed_ra_offset(sfsec);
+	/*
+	 * 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) {
+		*errp = 0;
+		return ra_offset;
+	}
+
+	/* Otherwise, get the RA offset from the FRE. */
+	return sframe_get_fre_offset(frep, SFRAME_FRE_RA_OFFSET_IDX, errp);
+}
diff --git a/lib/sframe/sframe_read.h b/lib/sframe/sframe_read.h
new file mode 100644
index 000000000000..30cf6be9cf00
--- /dev/null
+++ b/lib/sframe/sframe_read.h
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2023, Oracle and/or its affiliates.
+ */
+
+#ifndef SFRAME_READ_H
+#define SFRAME_READ_H
+
+#include <linux/types.h>
+
+#include "sframe.h"
+
+struct sframe_sec;
+
+#define MAX_NUM_STACK_OFFSETS 3
+
+#define MAX_STACK_OFFSET_NBYTES \
+	((SFRAME_FRE_OFFSET_4B * 2 * MAX_NUM_STACK_OFFSETS))
+
+/**
+ * struct sframe_fre - SFrame Frame Row Entry for the SFrame reader.
+ * @start_ip_offset: Start IP offset (from the start addr of the function)
+ * @fre_offsets: Byte array containing the stack offsets.
+ * @fre_info: Other information necessary to interpret the stack offsets.
+ *
+ * Providing such an abstraction helps decouple stack tracer from the
+ * binary format representation of the SFrame FRE. Each member is kept aligned
+ * at its natural boundary.
+ */
+struct sframe_fre {
+	uint32_t start_ip_offset;
+	unsigned char fre_offsets[MAX_STACK_OFFSET_NBYTES];
+	unsigned char fre_info;
+};
+
+#define SFRAME_ERR ((int) -1)
+
+/* SFrame version not supported. */
+#define SFRAME_ERR_VERSION_INVAL	(-2000)
+/* Corrupt SFrame. */
+#define SFRAME_ERR_INVAL		(-2001)
+/* SFrame Section Initialization Error. */
+#define SFRAME_ERR_INIT_INVAL		(-2002)
+/* Corrupt FDE. */
+#define SFRAME_ERR_FDE_INVAL		(-2003)
+/* Corrupt FRE. */
+#define SFRAME_ERR_FRE_INVAL		(-2004)
+/* FDE not found. */
+#define SFRAME_ERR_FDE_NOTFOUND		(-2005)
+/* FDEs not sorted. */
+#define SFRAME_ERR_FDE_NOTSORTED	(-2006)
+/* FRE not found. */
+#define SFRAME_ERR_FRE_NOTFOUND		(-2007)
+/* FRE offset not present. */
+#define SFRAME_ERR_FREOFFSET_NOPRESENT	(-2008)
+
+extern size_t sframe_sec_sizeof(void);
+
+extern int sframe_sec_init(struct sframe_sec *sfsec, const char *cf_buf,
+			   size_t cf_size);
+
+extern int sframe_sec_find_fre(struct sframe_sec *ctx, int32_t pc,
+			       struct sframe_fre *frep);
+
+extern unsigned int sframe_fre_get_base_reg_id(struct sframe_fre *fre,
+					       int *errp);
+extern int32_t sframe_fre_get_cfa_offset(struct sframe_sec *dtcx,
+					 struct sframe_fre *fre,
+					 int *errp);
+extern int32_t sframe_fre_get_fp_offset(struct sframe_sec *sfsec,
+					struct sframe_fre *fre,
+					int *errp);
+extern int32_t sframe_fre_get_ra_offset(struct sframe_sec *sfsec,
+					struct sframe_fre *fre,
+					int *errp);
+extern bool sframe_fre_get_ra_mangled_p(struct sframe_sec *sfsec,
+					struct sframe_fre *fre,
+					int *errp);
+
+#endif /* SFRAME_READ_H */
-- 
2.39.2


  parent reply	other threads:[~2023-05-26  5:32 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-05-26  5:32 [POC,V2 0/5] SFrame based stack tracer for user space in the kernel Indu Bhagat
2023-05-26  5:32 ` [POC,V2 1/5] Kconfig: x86: Add new config options for userspace unwinder Indu Bhagat
2023-05-26  5:32 ` [POC,V2 2/5] task_struct : add additional member for sframe state Indu Bhagat
2023-05-26  5:32 ` Indu Bhagat [this message]
2023-05-26  5:32 ` [POC,V2 4/5] sframe: add an SFrame format stack tracer Indu Bhagat
2023-05-26  5:32 ` [POC,V2 5/5] x86_64: invoke SFrame based stack tracer for user space Indu Bhagat
2023-05-26  7:56 ` [POC,V2 0/5] SFrame based stack tracer for user space in the kernel Steven Rostedt

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20230526053215.3617580-4-indu.bhagat@oracle.com \
    --to=indu.bhagat@oracle.com \
    --cc=linux-toolchains@vger.kernel.org \
    --cc=peterz@infradead.org \
    --cc=rostedt@goodmis.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).