/*
 * Copyright (C) 2022 - 2026, Stephan Mueller <smueller@chronox.de>
 *
 * License: see LICENSE file in root directory
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
 * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

#ifndef ASSEMBLER_SUPPORT_H
#define ASSEMBLER_SUPPORT_H

// clang-format off

/******************************************************************************
 * Hardening macros
 ******************************************************************************/

/***************************** X86 64 CET Support *****************************/
#if defined __ELF__ && defined __CET__
# ifdef __x86_64__
#  define LC_GNU_PROPERTY_ALIGN 3
# else
#  define LC_GNU_PROPERTY_ALIGN 2
# endif
# define LC_GNU_PROPERTY_TYPE 0xc0000002
# define LC_GNU_PROPERTY_DATA 3
#endif

/*************************** ARM BTI / PAC Supprt *****************************/
/*
 * References:
 *  - https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/enabling-pac-and-bti-on-aarch64
 *  - https://developer.arm.com/documentation/101028/0012/5--Feature-test-macros
 *  - https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst
 */
#if (defined(__ELF__) && defined(__aarch64__))

/* BTI Support */
# if defined(__ARM_FEATURE_BTI_DEFAULT) && __ARM_FEATURE_BTI_DEFAULT == 1
#  define BTI_J hint 36 /* bti j for jumps, IE br instructions */
#  define BTI_C hint 34  /* bti c for calls, IE bl instructions */
#  define GNU_PROPERTY_AARCH64_BTI 1 /* bit 0 GNU Notes is for BTI support */
# else
#  define BTI_J
#  define BTI_C
#  define GNU_PROPERTY_AARCH64_BTI 0
# endif

/* PAC Support */
# if defined(__ARM_FEATURE_PAC_DEFAULT)
#  if __ARM_FEATURE_PAC_DEFAULT & 1
#   define SIGN_LR hint 25 /* paciasp sign with the A key */
#   define VERIFY_LR hint 29 /* autiasp verify with the A key */
#  elif __ARM_FEATURE_PAC_DEFAULT & 2
#   define SIGN_LR hint 27 /* pacibsp sign with the b key */
#   define VERIFY_LR hint 32 /* autibsp verify with the b key */
#  endif
#  define GNU_PROPERTY_AARCH64_POINTER_AUTH 2 /* bit 1 GNU Notes is for PAC support */
# else
#  define SIGN_LR BTI_C
#  define VERIFY_LR
#  define GNU_PROPERTY_AARCH64_POINTER_AUTH 0
# endif

# if defined(__ARM_FEATURE_GCS_DEFAULT) && __ARM_FEATURE_GCS_DEFAULT == 1
#  define GNU_PROPERTY_AARCH64_GCS (1<<2)
# else
#  define GNU_PROPERTY_AARCH64_GCS 0 /* No GCS */
# endif

/* Add the BTI / PAC support to GNU Notes section */
# if GNU_PROPERTY_AARCH64_BTI != 0 ||                                          \
     GNU_PROPERTY_AARCH64_POINTER_AUTH != 0 ||                                 \
     GNU_PROPERTY_AARCH64_GCS != 0
#  define LC_GNU_PROPERTY_ALIGN 3 /* 2 on 32 bit systems, which we do not cover here */
#  define LC_GNU_PROPERTY_TYPE 0xc0000000
#  define LC_GNU_PROPERTY_DATA (GNU_PROPERTY_AARCH64_BTI |                     \
                                GNU_PROPERTY_AARCH64_POINTER_AUTH |            \
                                GNU_PROPERTY_AARCH64_GCS)
# endif
#endif

/****************************** Generic Helpers *******************************/

#ifdef LC_GNU_PROPERTY_ALIGN
# define LC_ASM_END							       \
	.pushsection ".note.gnu.property", "a";				       \
	.p2align LC_GNU_PROPERTY_ALIGN;					       \
	.long 1f - 0f;							       \
	.long 4f - 1f;							       \
	.long 5;							       \
0:									       \
	.asciz "GNU";							       \
1:									       \
	.p2align LC_GNU_PROPERTY_ALIGN;					       \
	.long LC_GNU_PROPERTY_TYPE;					       \
	.long 3f - 2f;							       \
2:									       \
	.long LC_GNU_PROPERTY_DATA;					       \
3:									       \
	.p2align LC_GNU_PROPERTY_ALIGN;					       \
4:									       \
	.popsection
#endif

#ifdef LC_ASM_END
# ifdef __x86_64__
#  define LC_ASM_ENTER_HARDENING endbr64
# elif defined(__i386__)
#  define LC_ASM_ENTER_HARDENING endbr32
# elif defined (__aarch64__)
#  define LC_ASM_ENTER_HARDENING SIGN_LR
# else
#  define LC_ASM_ENTER_HARDENING
# endif

# ifdef __aarch64__
#  define LC_ASM_LEAVE_HARDENING VERIFY_LR ;
# else
#  define LC_ASM_LEAVE_HARDENING
# endif
#else /* LC_ASM_END */
# define LC_ASM_ENTER_HARDENING
# define LC_ASM_LEAVE_HARDENING
# define LC_ASM_END
#endif /* LC_ASM_END */

/******************************************************************************
 * Assembler support
 ******************************************************************************/

#ifdef LINUX_KERNEL

#include <linux/linkage.h>
#if __has_include(<linux/objtool.h>)
# include <linux/objtool.h>
#else
# define stack_frame_non_standard
#endif

#if __has_include(<asm/frame.h>)
# include <asm/frame.h>
#else
# define FRAME_START
# define FRAME_END
#endif

# define SYM_FUNC_ENTER(name)

# define SYM_FUNC(name)	name
# define SYM_TYPE_OBJ(name)						       \
	.type SYM_FUNC(name),%object

# define SYM_TYPE_FUNC(name)						       \
	.type SYM_FUNC(name),%function

# define SYM_SIZE(name)							       \
	.size SYM_FUNC(name),.-SYM_FUNC(name)

# ifndef SYM_FUNC_START
#  define SYM_FUNC_START(name)						       \
	.global SYM_FUNC(name) ;					       \
	SYM_FUNC(name):
# endif

# ifndef SYM_FUNC_END
#  define SYM_FUNC_END(name)						       \
	SYM_TYPE_FUNC(name) ;						       \
	SYM_SIZE(name)
# endif

#define LC_FIPS_RODATA_SECTION .section .rodata

#else /* LINUX_KERNEL */

# define ANNOTATE_INTRA_FUNCTION_CALL

# ifdef __APPLE__
#  define SYM_FUNC(name)	_##name
#  define SYM_TYPE_OBJ(name)
#  define SYM_TYPE_FUNC(name)
#  define SYM_SIZE(name)

/* The Apple assembler does not support command separation with ";" */
#  define SYM_FUNC_START(name)						       \
	.global SYM_FUNC(name)

#  define SYM_FUNC_ENTER(name)						       \
	SYM_FUNC(name):							       \
	LC_ASM_ENTER_HARDENING ;

#  define SYM_FUNC_END(name)						       \
	SYM_TYPE_FUNC(name) ;						       \
	SYM_SIZE(name)

# define LC_FIPS_RODATA_SECTION .section .rodata

# elif (defined(__CYGWIN__) || defined(_WIN32))

#  define SYM_FUNC(name)	name

#  define SYM_TYPE_OBJ(name)						       \
	.type SYM_FUNC(name),%object

#  define SYM_TYPE_FUNC(name)

#  define SYM_SIZE(name)						       \
	.size SYM_FUNC(name),.-SYM_FUNC(name)

#  define SYM_FUNC_START(name)						       \
	.global SYM_FUNC(name) ;					       \
	SYM_FUNC(name):							       \
	LC_ASM_ENTER_HARDENING

#  define SYM_FUNC_ENTER(name)

#  define SYM_FUNC_END(name)

#  define FRAME_BEGIN

#  define FRAME_END

# define LC_FIPS_RODATA_SECTION .section .rodata

# else /* __APPLE__ */

#  define SYM_FUNC(name)	name

#  define SYM_TYPE_OBJ(name)						       \
	.type SYM_FUNC(name),%object

#  define SYM_TYPE_FUNC(name)						       \
	.type SYM_FUNC(name),%function

#  define SYM_SIZE(name)						       \
	.size SYM_FUNC(name),.-SYM_FUNC(name)

#  define SYM_FUNC_START(name)						       \
	.hidden SYM_FUNC(name) ;					       \
	.global SYM_FUNC(name) ;					       \
	SYM_FUNC(name):							       \
	LC_ASM_ENTER_HARDENING

#  define SYM_FUNC_ENTER(name)

#  define SYM_FUNC_END(name)						       \
	SYM_TYPE_FUNC(name) ;						       \
	SYM_SIZE(name)

#  define FRAME_BEGIN

#  define FRAME_END

#if (defined(__ELF__) && defined(LC_FIPS140_INTEGRITY_CHECKER))
#define LC_FIPS_RODATA_SECTION .section .lc_fips_rodata
#else
#define LC_FIPS_RODATA_SECTION .section .rodata
#endif

# endif /* __APPLE__ */

# define RET								       \
	LC_ASM_LEAVE_HARDENING						       \
	ret

# define STACK_FRAME_NON_STANDARD # ignored

#endif /* LINUX_KERNEL */

/*
 * FIPS 140 integrity check support
 */
#ifdef LINUX_KERNEL
#define LC_FIPS_RODATA_SECTION_ASM .section .rodata
#elif (defined(LC_EFI_ENVIRONMENT))
#define LC_FIPS_RODATA_SECTION_ASM .section .rodata
#elif (defined(__CYGWIN__) || defined(_WIN32))
#define LC_FIPS_RODATA_SECTION_ASM .section .rodata
#elif defined __ELF__
#define LC_FIPS_RODATA_SECTION_ASM .section .fips_rodata
#else
#define LC_FIPS_RODATA_SECTION_ASM .section .rodata
#endif

// clang-format on

#endif /* ASSEMBLER_SUPPORT_H */
