[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CALvbMcBv-vbJahUH_j+vxhsNnydbO7__RP1S2tg93HgUiTDv+w@mail.gmail.com>
Date: Sat, 13 Sep 2025 16:51:21 -0700
From: Andrew Pinski <andrew.pinski@....qualcomm.com>
To: Kees Cook <kees@...nel.org>
Cc: Qing Zhao <qing.zhao@...cle.com>, Andrew Pinski <pinskia@...il.com>,
Jakub Jelinek <jakub@...hat.com>, Martin Uecker <uecker@...raz.at>,
Richard Biener <rguenther@...e.de>, Joseph Myers <josmyers@...hat.com>,
Peter Zijlstra <peterz@...radead.org>, Jan Hubicka <hubicka@....cz>,
Richard Earnshaw <richard.earnshaw@....com>,
Richard Sandiford <richard.sandiford@....com>,
Marcus Shawcroft <marcus.shawcroft@....com>,
Kyrylo Tkachov <kyrylo.tkachov@....com>,
Kito Cheng <kito.cheng@...il.com>, Palmer Dabbelt <palmer@...belt.com>,
Andrew Waterman <andrew@...ive.com>,
Jim Wilson <jim.wilson.gcc@...il.com>,
Dan Li <ashimida.1990@...il.com>,
Sami Tolvanen <samitolvanen@...gle.com>,
Ramon de C Valle <rcvalle@...gle.com>,
Joao Moreira <joao@...rdrivepizza.com>,
Nathan Chancellor <nathan@...nel.org>,
Bill Wendling <morbo@...gle.com>, gcc-patches@....gnu.org,
linux-hardening@...r.kernel.org
Subject: Re: [PATCH v3 7/7] kcfi: Add regression test suite
On Sat, Sep 13, 2025 at 4:36 PM Kees Cook <kees@...nel.org> wrote:
>
> Adds a test suite for KCFI (Kernel Control Flow Integrity) ABI, covering
> core functionality, optimization and code generation, addressing,
> architecture-specific KCFI sequence emission, and integration with
> patchable function entry.
>
> Tests can be run via:
> make check-c RUNTESTFLAGS='kcfi.exp'
>
> gcc/testsuite/ChangeLog:
>
> * gcc.dg/kcfi/kcfi-adjacency.c: New test.
> * gcc.dg/kcfi/kcfi-basics.c: New test.
> * gcc.dg/kcfi/kcfi-call-sharing.c: New test.
> * gcc.dg/kcfi/kcfi-cold-partition.c: New test.
> * gcc.dg/kcfi/kcfi-complex-addressing.c: New test.
> * gcc.dg/kcfi/kcfi-ipa-robustness.c: New test.
> * gcc.dg/kcfi/kcfi-move-preservation.c: New test.
> * gcc.dg/kcfi/kcfi-no-sanitize-inline.c: New test.
> * gcc.dg/kcfi/kcfi-no-sanitize.c: New test.
> * gcc.dg/kcfi/kcfi-offset-validation.c: New test.
> * gcc.dg/kcfi/kcfi-patchable-basic.c: New test.
> * gcc.dg/kcfi/kcfi-patchable-entry-only.c: New test.
> * gcc.dg/kcfi/kcfi-patchable-large.c: New test.
> * gcc.dg/kcfi/kcfi-patchable-medium.c: New test.
> * gcc.dg/kcfi/kcfi-patchable-prefix-only.c: New test.
> * gcc.dg/kcfi/kcfi-pic-addressing.c: New test.
> * gcc.dg/kcfi/kcfi-retpoline-r11.c: New test.
> * gcc.dg/kcfi/kcfi-runtime.c: New test.
> * gcc.dg/kcfi/kcfi-tail-calls.c: New test.
> * gcc.dg/kcfi/kcfi-trap-encoding.c: New test.
> * gcc.dg/kcfi/kcfi-trap-section.c: New test.
> * gcc.dg/kcfi/kcfi.exp: New test.
>
> Signed-off-by: Kees Cook <kees@...nel.org>
> ---
> gcc/testsuite/gcc.dg/kcfi/kcfi-adjacency.c | 72 +++++++++
> gcc/testsuite/gcc.dg/kcfi/kcfi-basics.c | 108 +++++++++++++
> gcc/testsuite/gcc.dg/kcfi/kcfi-call-sharing.c | 84 ++++++++++
> .../gcc.dg/kcfi/kcfi-cold-partition.c | 136 ++++++++++++++++
> .../gcc.dg/kcfi/kcfi-complex-addressing.c | 135 ++++++++++++++++
> .../gcc.dg/kcfi/kcfi-ipa-robustness.c | 54 +++++++
> .../gcc.dg/kcfi/kcfi-move-preservation.c | 55 +++++++
> .../gcc.dg/kcfi/kcfi-no-sanitize-inline.c | 100 ++++++++++++
> gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize.c | 39 +++++
> .../gcc.dg/kcfi/kcfi-offset-validation.c | 48 ++++++
> .../gcc.dg/kcfi/kcfi-patchable-basic.c | 70 ++++++++
> .../gcc.dg/kcfi/kcfi-patchable-entry-only.c | 62 +++++++
> .../gcc.dg/kcfi/kcfi-patchable-large.c | 51 ++++++
> .../gcc.dg/kcfi/kcfi-patchable-medium.c | 60 +++++++
> .../gcc.dg/kcfi/kcfi-patchable-prefix-only.c | 60 +++++++
> .../gcc.dg/kcfi/kcfi-pic-addressing.c | 104 ++++++++++++
> .../gcc.dg/kcfi/kcfi-retpoline-r11.c | 50 ++++++
> gcc/testsuite/gcc.dg/kcfi/kcfi-runtime.c | 151 ++++++++++++++++++
> gcc/testsuite/gcc.dg/kcfi/kcfi-tail-calls.c | 142 ++++++++++++++++
> .../gcc.dg/kcfi/kcfi-trap-encoding.c | 54 +++++++
> gcc/testsuite/gcc.dg/kcfi/kcfi-trap-section.c | 41 +++++
> gcc/testsuite/gcc.dg/kcfi/kcfi.exp | 64 ++++++++
> 22 files changed, 1740 insertions(+)
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-adjacency.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-basics.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-call-sharing.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-cold-partition.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-complex-addressing.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-ipa-robustness.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-move-preservation.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize-inline.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-offset-validation.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-basic.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-entry-only.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-large.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-medium.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-prefix-only.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-pic-addressing.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-retpoline-r11.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-runtime.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-tail-calls.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-trap-encoding.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-trap-section.c
> create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi.exp
>
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-adjacency.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-adjacency.c
> new file mode 100644
> index 000000000000..becb47678df0
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-adjacency.c
> @@ -0,0 +1,72 @@
> +/* Test KCFI check/transfer adjacency - regression test for instruction
> + insertion. */
> +/* { dg-do compile } */
> +/* { dg-additional-options "-O2" } */
> +
> +/* This test ensures that KCFI security checks remain immediately adjacent
> + to their corresponding indirect calls/jumps, with no executable instructions
> + between the type ID check and the control flow transfer. */
> +
> +/* External function pointers to prevent optimization. */
> +extern void (*complex_func_ptr)(int, int, int, int);
> +extern int (*return_func_ptr)(int, int);
> +
> +/* Function with complex argument preparation that could tempt
> + the optimizer to insert instructions between KCFI check and call. */
> +__attribute__((noinline)) void test_complex_args(int a, int b, int c, int d) {
> + /* Complex argument expressions that might cause instruction scheduling. */
> + complex_func_ptr(a * 2, b + c, d - a, (a << 1) | b);
> +}
> +
> +/* Function with return value handling. */
> +__attribute__((noinline)) int test_return_value(int x, int y) {
> + /* Return value handling that shouldn't interfere with adjacency. */
> + int result = return_func_ptr(x + 1, y * 2);
> + return result + 1;
> +}
> +
> +/* Test struct field access that caused issues in try-catch.c. */
> +struct call_info {
> + void (*handler)(void);
> + int status;
> + int data;
> +};
> +
> +extern struct call_info *global_call_info;
> +
> +__attribute__((noinline)) void test_struct_field_call(void) {
> + /* This pattern caused adjacency issues before the fix. */
> + global_call_info->handler();
> +}
> +
> +/* Test conditional indirect call. */
> +__attribute__((noinline)) void test_conditional_call(int flag) {
> + if (flag) {
> + global_call_info->handler();
> + }
> +}
> +
> +/* Should have KCFI instrumentation for all indirect calls. */
> +
> +/* x86_64: Complete KCFI check sequence should be present. */
> +/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r1[01]d\n\taddl\t[^,]+, %r1[01]d\n\tje\t\.Lkcfi_call[0-9]+\n\.Lkcfi_trap[0-9]+:\n\tud2} { target x86_64-*-* } } } */
> +
> +/* AArch64: Complete KCFI check sequence should be present. */
> +/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-[0-9]+\]\n\tmov\tw17, #[0-9]+\n\tmovk\tw17, #[0-9]+, lsl #16\n\tcmp\tw16, w17\n\tb\.eq\t(\.Lkcfi_call[0-9]+)\n\.Lkcfi_trap[0-9]+:\n\tbrk\t#[0-9]+\n\1:\n\tblr\tx[0-9]+} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit: Complete KCFI check sequence should be present with stack
> + spilling. */
> +/* { dg-final { scan-assembler {push\t\{r0, r1\}\n\tldr\tr0, \[r[0-9]+, #-[0-9]+\]\n\tmovw\tr1, #[0-9]+\n\tmovt\tr1, #[0-9]+\n\tcmp\tr0, r1\n\tpop\t\{r0, r1\}\n\tbeq\t\.Lkcfi_call[0-9]+\n\.Lkcfi_trap[0-9]+:\n\tudf\t#[0-9]+\n\.Lkcfi_call[0-9]+:\n\tblx\tr[0-9]+} { target arm32 } } } */
> +
> +/* RISC-V: Complete KCFI check sequence should be present. */
> +/* { dg-final { scan-assembler {lw\tt1, -4\([a-z0-9]+\)\n\tlui\tt2, [0-9]+\n\taddiw\tt2, t2, -?[0-9]+\n\tbeq\tt1, t2, \.Lkcfi_call[0-9]+\n\.Lkcfi_trap[0-9]+:\n\tebreak} { target riscv*-*-* } } } */
> +
> +/* Should have trap section with entries. */
> +/* { dg-final { scan-assembler {\.kcfi_traps} { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler {\.kcfi_traps} { target riscv*-*-* } } } */
> +
> +/* AArch64 should NOT have trap section (uses brk immediate instead) */
> +/* { dg-final { scan-assembler-not {\.kcfi_traps} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit should NOT have trap section (uses udf immediate instead) */
> +/* { dg-final { scan-assembler-not {\.kcfi_traps} { target arm32 } } } */
I think it would be better to use check-function-bodies here rather
than scan-assembler for the sequences. Maybe each target should have
its own testcase rather than putting it all in one source.
Plus I think the target testcase should be part of the target patch
rather than its own patch to make it easier to review both things
together. Because while I was reviewing the aarch64 part I was
thinking where are the testcases for the aarch64 specific changes.
Thanks,
Andrew
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-basics.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-basics.c
> new file mode 100644
> index 000000000000..b0a9e11f1f3c
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-basics.c
> @@ -0,0 +1,108 @@
> +/* Test basic KCFI functionality - preamble generation. */
> +/* { dg-do compile } */
> +/* { dg-additional-options "-falign-functions=16" { target x86_64-*-* } } */
> +
> +/* Extern function declarations - should NOT get KCFI preambles. */
> +extern void external_func(void);
> +extern int external_func_int(int x);
> +
> +void regular_function(int x) {
> + /* This should get KCFI preamble. */
> +}
> +
> +void static_target_function(int x) {
> + /* Target function that can be called indirectly. */
> +}
> +
> +__attribute__((nocf_check))
> +void nocf_check_function(int x) {
> + /* This function has nocf_check attribute - should NOT get KCFI preamble. */
> +}
> +
> +static void static_caller(void) {
> + /* Static function that makes an indirect call
> + Should NOT get KCFI preamble (not address-taken)
> + But must generate KCFI check for the indirect call. */
> + void (*local_ptr)(int) = static_target_function;
> + local_ptr(42); /* This should generate KCFI check. */
> +}
> +
> +/* Make external_func address-taken. */
> +void (*func_ptr)(int) = regular_function;
> +void (*ext_ptr)(void) = external_func;
> +void (__attribute__((nocf_check)) *nocf_ptr)(int) = nocf_check_function;
> +
> +int main() {
> + func_ptr(42);
> + ext_ptr(); /* Indirect call to external_func. */
> + external_func_int(10); /* Direct call to external_func_int. */
> + static_caller(); /* Direct call to static function. */
> + return 0;
> +}
> +
> +/* Verify KCFI preamble exists for regular_function. */
> +/* { dg-final { scan-assembler {__cfi_regular_function:} } } */
> +
> +/* Verify KCFI preamble symbol comes before main function symbol. */
> +/* { dg-final { scan-assembler {__cfi_regular_function:.*regular_function:} } } */
> +
> +/* Target function should have preamble (address-taken). */
> +/* { dg-final { scan-assembler {__cfi_static_target_function:} } } */
> +
> +/* Static caller should NOT have preamble (it's only called directly,
> + not address-taken). */
> +/* { dg-final { scan-assembler-not {__cfi_static_caller:} } } */
> +
> +/* Function with nocf_check attribute should NOT have preamble. */
> +/* { dg-final { scan-assembler-not {__cfi_nocf_check_function:} } } */
> +
> +/* x86_64: Verify type ID in preamble (after NOPs, before function label) */
> +/* { dg-final { scan-assembler {__cfi_regular_function:\n\t+nop\n.*\n\t+movl\t+\$0x[0-9a-f]+, %eax} { target x86_64-*-* } } } */
> +
> +/* AArch64: Verify type ID word in preamble. */
> +/* { dg-final { scan-assembler {__cfi_regular_function:\n\t\.word\t0x[0-9a-f]+} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit: Verify type ID word in preamble. */
> +/* { dg-final { scan-assembler {__cfi_regular_function:\n\t\.word\t0x[0-9a-f]+} { target arm32 } } } */
> +
> +/* RISC-V: Verify type ID word in preamble */
> +/* { dg-final { scan-assembler {__cfi_regular_function:\n\t\.word\t0x[0-9a-f]+} { target riscv*-*-* } } } */
> +
> +/* x86_64: Static function should generate complete KCFI check sequence. */
> +/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r10d\n\taddl\t-4\(%r[a-z0-9]+\), %r10d\n\tje\t(\.Lkcfi_call[0-9]+)\n\.Lkcfi_trap[0-9]+:\n\tud2\n.*\n\1:\n\tcall} { target x86_64-*-* } } } */
> +
> +/* AArch64: Static function should generate complete KCFI check sequence. */
> +/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-4\]\n\tmov\tw17, #[0-9]+\n\tmovk\tw17, #[0-9]+, lsl #16\n\tcmp\tw16, w17\n\tb\.eq\t(\.Lkcfi_call[0-9]+)\n\.Lkcfi_trap[0-9]+:\n\tbrk\t#[0-9]+\n\1:\n\tblr} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit: Static function should generate complete KCFI check sequence
> + with stack spilling. */
> +/* { dg-final { scan-assembler {push\t\{r0, r1\}\n\tldr\tr0, \[r[0-9]+, #-4\]\n\tmovw\tr1, #[0-9]+\n\tmovt\tr1, #[0-9]+\n\tcmp\tr0, r1\n\tpop\t\{r0, r1\}\n\tbeq\t\.Lkcfi_call[0-9]+\n\.Lkcfi_trap[0-9]+:\n\tudf\t#[0-9]+\n\.Lkcfi_call[0-9]+:\n\tblx\tr[0-9]+} { target arm32 } } } */
> +
> +/* RISC-V: Static function should generate KCFI check for indirect call. */
> +/* { dg-final { scan-assembler {lw\tt1, -4\([a-z0-9]+\)\n\tlui\tt2, [0-9]+\n\taddiw\tt2, t2, -?[0-9]+\n\tbeq\tt1, t2, (\.Lkcfi_call[0-9]+)\n\.Lkcfi_trap[0-9]+:\n\tebreak\n\t\.section\t\.kcfi_traps,"ao",@progbits,\.text\n\.Lkcfi_entry[0-9]+:\n\t\.4byte\t\.Lkcfi_trap[0-9]+-\.Lkcfi_entry[0-9]+\n\t\.text\n\1:\n\tjalr} { target riscv*-*-* } } } */
> +
> +/* Extern functions should NOT get KCFI preambles. */
> +/* { dg-final { scan-assembler-not {__cfi_external_func:} } } */
> +/* { dg-final { scan-assembler-not {__cfi_external_func_int:} } } */
> +
> +/* Local functions should NOT get __kcfi_typeid_ symbols. */
> +/* Only external declarations that are address-taken should get __kcfi_typeid_ */
> +/* { dg-final { scan-assembler-not {__kcfi_typeid_regular_function} } } */
> +/* { dg-final { scan-assembler-not {__kcfi_typeid_main} } } */
> +
> +/* External address-taken functions should get __kcfi_typeid_ symbols. */
> +/* { dg-final { scan-assembler {__kcfi_typeid_external_func} } } */
> +
> +/* External functions that are only called directly should NOT get
> + __kcfi_typeid_ symbols. */
> +/* { dg-final { scan-assembler-not {__kcfi_typeid_external_func_int} } } */
> +
> +/* Should have trap section for KCFI checks. */
> +/* { dg-final { scan-assembler {\.kcfi_traps} { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler {\.kcfi_traps} { target riscv*-*-* } } } */
> +
> +/* AArch64 should NOT have trap section (uses brk immediate instead). */
> +/* { dg-final { scan-assembler-not {\.kcfi_traps} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit should NOT have trap section (uses udf immediate instead). */
> +/* { dg-final { scan-assembler-not {\.kcfi_traps} { target arm32 } } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-call-sharing.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-call-sharing.c
> new file mode 100644
> index 000000000000..f34d5f88547f
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-call-sharing.c
> @@ -0,0 +1,84 @@
> +/* Test KCFI check sharing bug - optimizer incorrectly shares KCFI checks
> + between different function types. */
> +/* { dg-do compile } */
> +/* { dg-additional-options "-O2" } */
> +
> +/* Reproduce the pattern from Linux kernel internal_create_group where:
> + - Two different function pointer types (is_visible vs is_bin_visible).
> + - Both get loaded into the same register (%rcx).
> + - Optimizer creates shared KCFI check with wrong type ID.
> + - This causes CFI failures in production kernel. */
> +
> +struct kobject { int dummy; };
> +struct attribute { int dummy; };
> +struct bin_attribute { int dummy; };
> +
> +struct attribute_group {
> + const char *name;
> + // Type ID A
> + int (*is_visible)(struct kobject *, struct attribute *, int);
> + // Type ID B
> + int (*is_bin_visible)(struct kobject *, const struct bin_attribute *, int);
> + struct attribute **attrs;
> + const struct bin_attribute **bin_attrs;
> +};
> +
> +/* Function that mimics __first_visible from kernel - gets inlined into
> + caller. */
> +static int __first_visible(const struct attribute_group *grp, struct kobject *kobj)
> +{
> + /* Path 1: Call is_visible function pointer. */
> + if (grp->attrs && grp->attrs[0] && grp->is_visible)
> + return grp->is_visible(kobj, grp->attrs[0], 0);
> +
> + /* Path 2: Call is_bin_visible function pointer. */
> + if (grp->bin_attrs && grp->bin_attrs[0] && grp->is_bin_visible)
> + return grp->is_bin_visible(kobj, grp->bin_attrs[0], 0);
> +
> + return 0;
> +}
> +
> +/* Main function that triggers the optimization bug. */
> +int test_kcfi_check_sharing(struct kobject *kobj, const struct attribute_group *grp)
> +{
> + /* This should inline __first_visible and create the problematic pattern where:
> + 1. Both function pointers get loaded into same register.
> + 2. Optimizer shares KCFI check between them.
> + 3. Uses wrong type ID for one of the calls. */
> + return __first_visible(grp, kobj);
> +}
> +
> +/* Each indirect call should have its own KCFI check with correct type ID.
> +
> + Should see:
> + 1. KCFI check for is_visible call with is_visible type ID.
> + 2. KCFI check for is_bin_visible call with is_bin_visible type ID. */
> +
> +/* Verify we have TWO different KCFI check sequences. */
> +/* Each check should have different type ID constants. */
> +/* x86: { dg-final { scan-assembler-times {movl\s+\$-?[0-9]+,\s+%r10d} 2 { target i?86-*-* x86_64-*-* } } } */
> +/* AArch64: { dg-final { scan-assembler-times {mov\s+w17, #[0-9]+} 2 { target aarch64*-*-* } } } */
> +/* ARM 32-bit: { dg-final { scan-assembler-times {movw\s+r1, #[0-9]+} 2 { target arm32 } } } */
> +/* RISC-V: { dg-final { scan-assembler-times {lui\tt2, [0-9]+} 2 { target riscv*-*-* } } } */
> +
> +/* Verify the checks use DIFFERENT type IDs (not shared).
> + We should NOT see the same type ID used twice - that would indicate
> + sharing bug. */
> +/* x86: { dg-final { scan-assembler-not {movl\s+\$(-?[0-9]+),\s+%r10d.*movl\s+\$\1,\s+%r10d} { target i?86-*-* x86_64-*-* } } } */
> +/* AArch64: { dg-final { scan-assembler-not {mov\s+w17, #([0-9]+).*mov\s+w17, #\1} { target aarch64*-*-* } } } */
> +/* ARM 32-bit: { dg-final { scan-assembler-not {movw\s+r1, #([0-9]+).*movw\s+r1, #\1} { target arm32 } } } */
> +/* RISC-V: { dg-final { scan-assembler-not {lui\s+t2, ([0-9]+)\s.*lui\s+t2, \1\s} { target riscv*-*-* } } } */
> +
> +/* Verify each call follows its own check (not shared) */
> +/* Should have 2 separate trap instructions. */
> +/* x86: { dg-final { scan-assembler-times {ud2} 2 { target i?86-*-* x86_64-*-* } } } */
> +/* AArch64: { dg-final { scan-assembler-times {brk\s+#[0-9]+} 2 { target aarch64*-*-* } } } */
> +/* ARM 32-bit: { dg-final { scan-assembler-times {udf\s+#[0-9]+} 2 { target arm32 } } } */
> +/* RISC-V: { dg-final { scan-assembler-times {ebreak} 2 { target riscv*-*-* } } } */
> +
> +/* Verify 2 separate call sites. */
> +/* x86: { dg-final { scan-assembler-times {jmp\s+\*%[a-z0-9]+} 2 { target i?86-*-* x86_64-*-* } } } */
> +/* AArch64: Allow both blr (regular call) and br (tail call) */
> +/* AArch64: { dg-final { scan-assembler-times {br\tx[0-9]+} 2 { target aarch64*-*-* } } } */
> +/* ARM 32-bit: { dg-final { scan-assembler-times {bx\s+(?:r[0-9]+|ip)} 2 { target arm32 } } } */
> +/* RISC-V: { dg-final { scan-assembler-times {jalr\t[a-z0-9]+} 2 { target riscv*-*-* } } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-cold-partition.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-cold-partition.c
> new file mode 100644
> index 000000000000..17def558ada4
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-cold-partition.c
> @@ -0,0 +1,136 @@
> +/* Test KCFI cold function and cold partition behavior. */
> +/* { dg-do compile } */
> +/* { dg-additional-options "-O2" } */
> +/* { dg-additional-options "-freorder-blocks-and-partition" { target freorder } } */
> +
> +void regular_function(void) {
> + /* Regular function should get preamble. */
> +}
> +
> +/* Cold-attributed function should STILL get preamble (it's a regular
> + function, just marked cold). */
> +__attribute__((cold))
> +void cold_attributed_function(void) {
> + /* This function has cold attribute but should still get KCFI preamble. */
> +}
> +
> +/* Hot-attributed function should get preamble. */
> +__attribute__((hot))
> +void hot_attributed_function(void) {
> + /* This function is explicitly hot and should get KCFI preamble. */
> +}
> +
> +/* Global to prevent optimization from eliminating cold paths. */
> +extern void abort(void);
> +
> +/* Additional function to test that normal functions still get preambles. */
> +__attribute__((noinline))
> +int another_regular_function(int x) {
> + return x + 42;
> +}
> +
> +/* Function designed to generate cold partitions under optimization. */
> +__attribute__((noinline))
> +void function_with_cold_partition(int condition) {
> + /* Hot path - very likely to execute. */
> + if (__builtin_expect(condition == 42, 1)) {
> + /* Simple hot path that optimizer will keep inline. */
> + return;
> + }
> +
> + /* Cold paths that actually do something to prevent elimination. */
> + if (__builtin_expect(condition < 0, 0)) {
> + /* Error path 1 - call abort to prevent elimination. */
> + abort();
> + }
> +
> + if (__builtin_expect(condition > 1000000, 0)) {
> + /* Error path 2 - call abort to prevent elimination. */
> + abort();
> + }
> +
> + if (__builtin_expect(condition == 999999, 0)) {
> + /* Error path 3 - more substantial cold code. */
> + volatile int sum = 0;
> + for (volatile int i = 0; i < 100; i++) {
> + sum += i * condition;
> + }
> + if (sum > 0)
> + abort();
> + }
> +
> + /* More cold paths - switch with many unlikely cases. */
> + switch (condition) {
> + case 1000001: case 1000002: case 1000003: case 1000004: case 1000005:
> + case 1000006: case 1000007: case 1000008: case 1000009: case 1000010:
> + /* Each case does some work before abort. */
> + volatile int work = condition * 2;
> + if (work > 0) abort();
> + break;
> + default:
> + if (condition != 42) {
> + /* Fallback cold path - substantial work. */
> + volatile int result = 0;
> + for (volatile int j = 0; j < condition % 50; j++) {
> + result += j;
> + }
> + if (result >= 0) abort();
> + }
> + }
> +}
> +
> +/* Test function pointers to ensure address-taken detection works. */
> +void test_function_pointers(void) {
> + void (*regular_ptr)(void) = regular_function;
> + void (*cold_ptr)(void) = cold_attributed_function;
> + void (*hot_ptr)(void) = hot_attributed_function;
> +
> + regular_ptr();
> + cold_ptr();
> + hot_ptr();
> +}
> +
> +int main() {
> + regular_function();
> + cold_attributed_function();
> + hot_attributed_function();
> + function_with_cold_partition(42); /* Normal case - stay in hot path. */
> + another_regular_function(5);
> + test_function_pointers();
> + return 0;
> +}
> +
> +/* Regular function should have preamble. */
> +/* { dg-final { scan-assembler "__cfi_regular_function:" } } */
> +
> +/* Cold-attributed function should STILL have preamble (it's a legitimate function) */
> +/* { dg-final { scan-assembler "__cfi_cold_attributed_function:" } } */
> +
> +/* Hot-attributed function should have preamble. */
> +/* { dg-final { scan-assembler "__cfi_hot_attributed_function:" } } */
> +
> +/* Function that generates cold partitions should have preamble for main entry. */
> +/* { dg-final { scan-assembler "__cfi_function_with_cold_partition:" } } */
> +
> +/* Address-taken functions should have preambles. */
> +/* { dg-final { scan-assembler "__cfi_test_function_pointers:" } } */
> +
> +/* The function should generate a .cold partition (only on targets that support freorder) */
> +/* { dg-final { scan-assembler "function_with_cold_partition\\.cold:" { target freorder } } } */
> +
> +/* The .cold partition should NOT get a __cfi_ preamble since it's never
> + reached via indirect calls. */
> +/* { dg-final { scan-assembler-not "__cfi_function_with_cold_partition\\.cold:" { target freorder } } } */
> +
> +/* Additional regular function should get preamble. */
> +/* { dg-final { scan-assembler "__cfi_another_regular_function:" } } */
> +
> +/* Test coverage summary:
> + 1. Cold-attributed function (__attribute__((cold))): SHOULD get preamble
> + 2. Cold partition (-freorder-blocks-and-partition): should NOT get preamble
> + 3. IPA split .part function (split_part=true): Logic in place, would skip if triggered
> +
> + Note: IPA function splitting (creating .part functions with split_part=true) requires
> + specific optimization conditions that are difficult to trigger reliably in tests.
> + The KCFI logic correctly handles this case using the split_part flag check.
> +*/
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-complex-addressing.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-complex-addressing.c
> new file mode 100644
> index 000000000000..b9a8955b0899
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-complex-addressing.c
> @@ -0,0 +1,135 @@
> +/* Test KCFI with complex addressing modes (structure members, array
> + elements). This is a regression test for the change_address_1 RTL
> + error that occurred when target_addr was PLUS(reg, offset) instead
> + of a simple register. */
> +/* { dg-do compile } */
> +/* { dg-additional-options "-O2" } */
> +
> +struct function_table {
> + int (*callback1)(int);
> + int (*callback2)(int, int);
> + void (*callback3)(void);
> + int (*callback4)(void *, void *, void *, void *, void *, void *);
> + int data;
> +};
> +
> +static int handler1(int x) {
> + return x * 2;
> +}
> +
> +static int handler2(int x, int y) {
> + return x + y;
> +}
> +
> +static void handler3(void) {
> + /* Empty handler. */
> +}
> +
> +/* Test indirect calls through structure members - this creates
> + PLUS(reg, offset) addressing. */
> +int test_struct_members(struct function_table *table) {
> + int result = 0;
> +
> + /* These indirect calls will generate complex addressing modes:
> + * call *(%rdi) - callback1 at offset 0
> + * call *8(%rdi) - callback2 at offset 8
> + * call *16(%rdi) - callback3 at offset 16
> + * KCFI must handle PLUS(reg, struct_offset) + kcfi_offset. */
> +
> + result += table->callback1(10);
> + result += table->callback2(5, 7);
> + table->callback3();
> +
> + return result;
> +}
> +
> +/* Test indirect calls through array elements - another source of
> + complex addressing. */
> +typedef int (*func_array_t)(int);
> +
> +int test_array_elements(func_array_t functions[], int index) {
> + /* This creates addressing like MEM[PLUS(PLUS(reg, index*8), 0)]
> + which should be simplified to MEM[PLUS(reg, index*8)]. */
> + return functions[index](42);
> +}
> +
> +/* Test with global structure. */
> +static struct function_table global_table = {
> + .callback1 = handler1,
> + .callback2 = handler2,
> + .callback3 = handler3,
> + .data = 100
> +};
> +
> +int test_global_struct(void) {
> + /* Access through global structure - may generate different
> + addressing patterns. */
> + return global_table.callback1(20) + global_table.callback2(3, 4);
> +}
> +
> +/* Test nested structure access. */
> +struct nested_table {
> + struct function_table inner;
> + int extra_data;
> +};
> +
> +int test_nested_struct(struct nested_table *nested) {
> + /* Even more complex addressing: nested structure member access. */
> + return nested->inner.callback1(15);
> +}
> +
> +int test_many_args(void *one, void *two, void *three, void *four, void *five, void *six)
> +{
> + return (unsigned long)one + (unsigned long)two + (unsigned long)three
> + + (unsigned long)four + (unsigned long)five + (unsigned long)six;
> +}
> +
> +int main() {
> + struct function_table local_table = {
> + .callback1 = handler1,
> + .callback2 = handler2,
> + .callback3 = handler3,
> + .callback4 = test_many_args,
> + .data = 50
> + };
> +
> + func_array_t func_array[] = { handler1, handler1, handler1 };
> +
> + int result = 0;
> + result += test_struct_members(&local_table);
> + result += test_array_elements(func_array, 1);
> + result += test_global_struct();
> +
> + struct nested_table nested = { .inner = local_table, .extra_data = 200 };
> + result += test_nested_struct(&nested);
> +
> + result += local_table.callback4(handler1, handler2, handler3, &result, main, &local_table);
> +
> + return result;
> +}
> +
> +/* Verify that all address-taken functions get KCFI preambles. */
> +/* { dg-final { scan-assembler {__cfi_handler1:} } } */
> +/* { dg-final { scan-assembler {__cfi_handler2:} } } */
> +/* { dg-final { scan-assembler {__cfi_handler3:} } } */
> +/* { dg-final { scan-assembler {__cfi_test_many_args:} } } */
> +
> +/* x86_64: Verify KCFI checks are generated for indirect calls through
> + complex addressing. */
> +/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r10d\n\taddl\t-4\(%r[a-z0-9]+\), %r10d} { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler {ud2} { target x86_64-*-* } } } */
> +
> +/* AArch64: Verify KCFI checks for complex addressing. */
> +/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-4\]} { target aarch64*-*-* } } } */
> +/* { dg-final { scan-assembler {brk} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit: Verify KCFI checks for complex addressing with stack spilling. */
> +/* { dg-final { scan-assembler {ldr\tr0, \[r[0-9]+, #-4\]} { target arm32 } } } */
> +/* { dg-final { scan-assembler {udf} { target arm32 } } } */
> +
> +/* RISC-V: Verify KCFI check sequence for complex addressing. */
> +/* { dg-final { scan-assembler {lw\tt1, -4\([a-z0-9]+\)\n\tlui\tt2, [0-9]+\n\taddiw\tt2, t2, -?[0-9]+\n\tbeq\tt1, t2, \.Lkcfi_call[0-9]+\n\.Lkcfi_trap[0-9]+:\n\tebreak} { target riscv*-*-* } } } */
> +
> +/* Should have trap section for x86 and RISC-V only. */
> +/* { dg-final { scan-assembler {\.kcfi_traps} { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler {\.kcfi_traps} { target riscv*-*-* } } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-ipa-robustness.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-ipa-robustness.c
> new file mode 100644
> index 000000000000..a43bcd4f3e3f
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-ipa-robustness.c
> @@ -0,0 +1,54 @@
> +/* Test KCFI IPA pass robustness with compiler-generated constructs. */
> +/* { dg-do compile } */
> +/* { dg-additional-options "-O2" } */
> +
> +#include <stddef.h>
> +
> +/* Test various compiler-generated constructs that could confuse IPA pass. */
> +
> +/* static_assert - this was causing the original crash. */
> +typedef struct {
> + int field1;
> + char field2;
> +} test_struct_t;
> +
> +static_assert(offsetof(test_struct_t, field1) == 0, "layout check 1");
> +static_assert(offsetof(test_struct_t, field2) == 4, "layout check 2");
> +static_assert(sizeof(test_struct_t) >= 5, "size check");
> +
> +/* Regular functions that should get KCFI analysis. */
> +void regular_function(void) {
> + /* Should get KCFI preamble. */
> +}
> +
> +static void static_function(void) {
> + /* With -O2: correctly identified as not address-taken, no preamble. */
> +}
> +
> +void address_taken_function(void) {
> + /* Should get KCFI preamble (address taken below) */
> +}
> +
> +/* Function pointer to create address-taken scenario. */
> +void (*func_ptr)(void) = address_taken_function;
> +
> +/* More static_asserts mixed with function definitions. */
> +static_assert(sizeof(void*) >= 4, "pointer size check");
> +
> +int main(void) {
> + regular_function(); /* Direct call. */
> + static_function(); /* Direct call to static. */
> + func_ptr(); /* Indirect call. */
> +
> + static_assert(sizeof(int) == 4, "int size check");
> +
> + return 0;
> +}
> +
> +/* Verify KCFI preambles are generated appropriately. */
> +/* { dg-final { scan-assembler "__cfi_regular_function:" } } */
> +/* { dg-final { scan-assembler "__cfi_address_taken_function:" } } */
> +/* { dg-final { scan-assembler "__cfi_main:" } } */
> +
> +/* With -O2: static_function correctly identified as not address-taken. */
> +/* { dg-final { scan-assembler-not "__cfi_static_function:" } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-move-preservation.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-move-preservation.c
> new file mode 100644
> index 000000000000..50029d136716
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-move-preservation.c
> @@ -0,0 +1,55 @@
> +/* Test that KCFI preserves function pointer moves at -O2 optimization.
> + This test ensures that the combine pass doesn't incorrectly optimize away
> + the move instruction needed to transfer function pointers from argument
> + registers to the target registers used by KCFI patterns. */
> +
> +/* { dg-do compile } */
> +/* { dg-additional-options "-O2 -std=gnu11" } */
> +
> +static int called_count = 0;
> +
> +/* Function taking one argument, returning void. */
> +static __attribute__((noinline)) void increment_void(int *counter)
> +{
> + (*counter)++;
> +}
> +
> +/* Function taking one argument, returning int. */
> +static __attribute__((noinline)) int increment_int(int *counter)
> +{
> + (*counter)++;
> + return *counter;
> +}
> +
> +/* Don't allow the compiler to inline the calls. */
> +static __attribute__((noinline)) void indirect_call(void (*func)(int *))
> +{
> + func(&called_count);
> +}
> +
> +int main(void)
> +{
> + /* This should work - matching prototype. */
> + indirect_call(increment_void);
> +
> + /* This should trap - mismatched prototype. */
> + indirect_call((void *)increment_int);
> +
> + return 0;
> +}
> +
> +/* Verify complete KCFI check sequence with preserved move instruction. At
> + -O2, the combine pass previously optimized away the move from %rdi to %rax,
> + breaking KCFI. Verify the full sequence is preserved. */
> +
> +/* x86_64: Complete KCFI sequence with move preservation and indirect jump. */
> +/* { dg-final { scan-assembler {(indirect_call):.*\n.*movq\s+%rdi,\s+(%rax)\n.*movl\s+\$[0-9]+,\s+%r10d\n\taddl\s+-4\(\2\),\s+%r10d\n\tje\s+\.Lkcfi_call[0-9]+\n\.Lkcfi_trap[0-9]+:\n\tud2.*\.Lkcfi_call[0-9]+:\n\tjmp\s+\*\2.*\.size\s+\1,\s+\.-\1} { target x86_64-*-* } } } */
> +
> +/* AArch64: Complete KCFI sequence with move preservation and indirect branch. */
> +/* { dg-final { scan-assembler {(indirect_call):.*\n.*mov\s+(x[0-9]+),\s+x0\n.*ldur\s+w16,\s+\[\2,\s+#-4\]\n\tmov\s+w17,\s+#[0-9]+\n\tmovk\s+w17,\s+#[0-9]+,\s+lsl\s+#16\n\tcmp\s+w16,\s+w17\n\tb\.eq\s+\.Lkcfi_call[0-9]+\n\.Lkcfi_trap[0-9]+:\n\tbrk\s+#[0-9]+.*\.Lkcfi_call[0-9]+:\n\tbr\s+\2.*\.size\s+\1,\s+\.-\1} { target aarch64*-*-* } } } */
> +
> +/* ARM32: Complete KCFI sequence with move preservation and indirect branch. */
> +/* { dg-final { scan-assembler {(indirect_call):.*\n.*mov\s+(r[0-9]+),\s+r0\n.*push\s+\{r0,\s+r1\}\n\tldr\s+r0,\s+\[\2,\s+#-4\]\n\tmovw\s+r1,\s+#[0-9]+\n\tmovt\s+r1,\s+#[0-9]+\n\tcmp\s+r0,\s+r1\n\tpop\s+\{r0,\s+r1\}\n\tbeq\s+\.Lkcfi_call[0-9]+\n\.Lkcfi_trap[0-9]+:\n\tudf\s+#[0-9]+.*\.Lkcfi_call[0-9]+:\n\tbx\s+\2.*\.size\s+\1,\s+\.-\1} { target arm32 } } } */
> +
> +/* RISC-V: Complete KCFI sequence with move preservation and indirect jump. */
> +/* { dg-final { scan-assembler {(indirect_call):.*mv\s+(a[0-9]+),a0.*lw\s+t1,\s+-4\(\2\).*lui\s+t2,\s+[0-9]+.*addiw\s+t2,\s+t2,\s+-?[0-9]+.*beq\s+t1,\s+t2,\s+\.Lkcfi_call[0-9]+.*ebreak.*jalr\s+zero,\s+\2,\s+0.*\.size\s+\1,\s+\.-\1} { target riscv64-*-* } } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize-inline.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize-inline.c
> new file mode 100644
> index 000000000000..c43d8014ff2d
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize-inline.c
> @@ -0,0 +1,100 @@
> +/* Test that no_sanitize("kcfi") attribute is preserved during inlining. */
> +/* { dg-do compile } */
> +/* { dg-additional-options "-O2" } */
> +
> +extern void external_side_effect(int value);
> +
> +/* Regular function (should get KCFI checks) */
> +__attribute__((noinline))
> +void normal_function(void (*callback)(int))
> +{
> + /* This indirect call must generate KCFI checks. */
> + callback(300);
> + external_side_effect(300);
> +}
> +
> +/* Regular function marked with no_sanitize("kcfi") (positive control) */
> +__attribute__((noinline, no_sanitize("kcfi")))
> +void sensitive_non_inline_function(void (*callback)(int))
> +{
> + /* This indirect call should NOT generate KCFI checks. */
> + callback(100);
> + external_side_effect(100);
> +}
> +
> +/* Function marked with both no_sanitize("kcfi") and always_inline. */
> +__attribute__((always_inline, no_sanitize("kcfi")))
> +static inline void sensitive_inline_function(void (*callback)(int))
> +{
> + /* This indirect call should NOT generate KCFI checks when inlined. */
> + callback(42);
> + external_side_effect(42);
> +}
> +
> +/* Explicit wrapper for testing sensitive_inline_function behavior. */
> +__attribute__((noinline))
> +void wrap_sensitive_inline(void (*callback)(int))
> +{
> + sensitive_inline_function(callback);
> +}
> +
> +/* Function marked with only always_inline (should get KCFI checks) */
> +__attribute__((always_inline))
> +static inline void normal_inline_function(void (*callback)(int))
> +{
> + /* This indirect call must generate KCFI checks when inlined. */
> + callback(200);
> + external_side_effect(200);
> +}
> +
> +/* Explicit wrapper for testing normal_inline_function behavior. */
> +__attribute__((noinline))
> +void wrap_normal_inline(void (*callback)(int))
> +{
> + normal_inline_function(callback);
> +}
> +
> +void test_callback(int value)
> +{
> + external_side_effect(value);
> +}
> +
> +static void (*volatile function_pointer)(int) = test_callback;
> +
> +int main(void)
> +{
> + void (*fn_ptr)(int) = function_pointer;
> +
> + normal_function(fn_ptr);
> + wrap_normal_inline(fn_ptr);
> + sensitive_non_inline_function(fn_ptr);
> + wrap_sensitive_inline(fn_ptr);
> +
> + return 0;
> +}
> +
> +/* Verify correct number of KCFI checks: exactly 2 */
> +/* { dg-final { scan-assembler-times {ud2} 2 { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler-times {brk\s+#[0-9]+} 2 { target aarch64*-*-* } } } */
> +/* { dg-final { scan-assembler-times {udf\s+#[0-9]+} 2 { target arm32 } } } */
> +/* { dg-final { scan-assembler-times {ebreak} 2 { target riscv*-*-* } } } */
> +
> +/* Positive controls: these should have KCFI checks. */
> +/* { dg-final { scan-assembler {normal_function:.*ud2.*\.size\s+normal_function} { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler {wrap_normal_inline:.*ud2.*\.size\s+wrap_normal_inline} { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler {normal_function:.*brk\s+#[0-9]+.*\.size\s+normal_function} { target aarch64*-*-* } } } */
> +/* { dg-final { scan-assembler {wrap_normal_inline:.*brk\s+#[0-9]+.*\.size\s+wrap_normal_inline} { target aarch64*-*-* } } } */
> +/* { dg-final { scan-assembler {normal_function:.*udf\t#[0-9]+.*\.size\s+normal_function} { target arm32 } } } */
> +/* { dg-final { scan-assembler {wrap_normal_inline:.*udf\t#[0-9]+.*\.size\s+wrap_normal_inline} { target arm32 } } } */
> +/* { dg-final { scan-assembler {normal_function:.*ebreak.*\.size\s+normal_function} { target riscv*-*-* } } } */
> +/* { dg-final { scan-assembler {wrap_normal_inline:.*ebreak.*\.size\s+wrap_normal_inline} { target riscv*-*-* } } } */
> +
> +/* Negative controls: these should NOT have KCFI checks. */
> +/* { dg-final { scan-assembler-not {sensitive_non_inline_function:.*ud2.*\.size\s+sensitive_non_inline_function} { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler-not {wrap_sensitive_inline:.*ud2.*\.size\s+wrap_sensitive_inline} { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler-not {sensitive_non_inline_function:.*brk\s+#[0-9]+.*\.size\s+sensitive_non_inline_function} { target aarch64*-*-* } } } */
> +/* { dg-final { scan-assembler-not {wrap_sensitive_inline:.*brk\s+#[0-9]+.*\.size\s+wrap_sensitive_inline} { target aarch64*-*-* } } } */
> +/* { dg-final { scan-assembler-not {sensitive_non_inline_function:[^\n]*udf\t#[0-9]+[^\n]*\.size\tsensitive_non_inline_function} { target arm32 } } } */
> +/* { dg-final { scan-assembler-not {wrap_sensitive_inline:[^\n]*udf\t#[0-9]+[^\n]*\.size\twrap_sensitive_inline} { target arm32 } } } */
> +/* { dg-final { scan-assembler-not {sensitive_non_inline_function:.*ebreak.*\.size\s+sensitive_non_inline_function} { target riscv*-*-* } } } */
> +/* { dg-final { scan-assembler-not {wrap_sensitive_inline:.*ebreak.*\.size\s+wrap_sensitive_inline} { target riscv*-*-* } } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize.c
> new file mode 100644
> index 000000000000..6f1a558c0820
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize.c
> @@ -0,0 +1,39 @@
> +/* Test KCFI with no_sanitize attribute. */
> +/* { dg-do compile } */
> +
> +void target_function(void) {
> + /* This should get KCFI preamble. */
> +}
> +
> +void caller_with_checks(void) {
> + /* This function should generate KCFI checks. */
> + void (*func_ptr)(void) = target_function;
> + func_ptr();
> +}
> +
> +__attribute__((no_sanitize("kcfi")))
> +void caller_no_checks(void) {
> + /* This function should NOT generate KCFI checks due to no_sanitize. */
> + void (*func_ptr)(void) = target_function;
> + func_ptr();
> +}
> +
> +int main() {
> + caller_with_checks(); /* This should generate checks inside. */
> + caller_no_checks(); /* This should NOT generate checks inside. */
> + return 0;
> +}
> +
> +/* All functions should get preambles regardless of no_sanitize. */
> +/* { dg-final { scan-assembler "__cfi_target_function:" } } */
> +/* { dg-final { scan-assembler "__cfi_caller_with_checks:" } } */
> +/* { dg-final { scan-assembler "__cfi_caller_no_checks:" } } */
> +/* { dg-final { scan-assembler "__cfi_main:" } } */
> +
> +/* caller_with_checks() should generate KCFI check.
> + caller_no_checks() should NOT generate KCFI check (no_sanitize).
> + So a total of exactly 1 KCFI check in the entire program. */
> +/* { dg-final { scan-assembler-times {addl\t-4\(%r[ad]x\), %r1[01]d} 1 { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler-times {ldur\tw16, \[x[0-9]+, #-4\]} 1 { target aarch64-*-* } } } */
> +/* { dg-final { scan-assembler-times {ldr\tr0, \[r[0-9]+, #-4\]} 1 { target arm32 } } } */
> +/* { dg-final { scan-assembler-times {lw\tt1, -[0-9]+\(} 1 { target riscv*-*-* } } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-offset-validation.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-offset-validation.c
> new file mode 100644
> index 000000000000..f93a042d9752
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-offset-validation.c
> @@ -0,0 +1,48 @@
> +/* Test KCFI call-site offset validation across architectures. */
> +/* { dg-do compile } */
> +/* { dg-additional-options "-falign-functions=16" { target x86_64-*-* } } */
> +
> +void target_func_a(void) { }
> +void target_func_b(int x) { }
> +void target_func_c(int x, int y) { }
> +
> +int main() {
> + void (*ptr_a)(void) = target_func_a;
> + void (*ptr_b)(int) = target_func_b;
> + void (*ptr_c)(int, int) = target_func_c;
> +
> + /* Multiple indirect calls. */
> + ptr_a();
> + ptr_b(1);
> + ptr_c(1, 2);
> +
> + return 0;
> +}
> +
> +/* Should have KCFI preambles for all functions. */
> +/* { dg-final { scan-assembler "__cfi_target_func_a:" } } */
> +/* { dg-final { scan-assembler "__cfi_target_func_b:" } } */
> +/* { dg-final { scan-assembler "__cfi_target_func_c:" } } */
> +
> +/* x86_64: All call sites should use -4 offset for KCFI type ID loads, even
> + with -falign-functions=16 (we're not using patchable entries here). */
> +/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r10d\n\taddl\t-4\(%r[a-z0-9]+\), %r10d} { target x86_64-*-* } } } */
> +
> +/* AArch64: All call sites should use -4 offset. */
> +/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-4\]} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit: All call sites should use -4 offset with stack spilling. */
> +/* { dg-final { scan-assembler {ldr\tr0, \[r[0-9]+, #-4\]} { target arm32 } } } */
> +
> +/* RISC-V: All call sites should use -4 offset. */
> +/* { dg-final { scan-assembler {lw\tt1, -4\(} { target riscv*-*-* } } } */
> +
> +/* Should have trap section. */
> +/* { dg-final { scan-assembler {\.kcfi_traps} { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler {\.kcfi_traps} { target riscv*-*-* } } } */
> +
> +/* AArch64 should NOT have trap section (uses brk immediate instead) */
> +/* { dg-final { scan-assembler-not {\.kcfi_traps} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit should NOT have trap section (uses udf immediate instead) */
> +/* { dg-final { scan-assembler-not {\.kcfi_traps} { target arm32 } } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-basic.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-basic.c
> new file mode 100644
> index 000000000000..a2d0ef0c6ff6
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-basic.c
> @@ -0,0 +1,70 @@
> +/* Test KCFI with patchable function entries - basic case. */
> +/* { dg-do compile } */
> +/* { dg-additional-options "-fpatchable-function-entry=5,2" } */
> +/* { dg-additional-options "-falign-functions=16" { target x86_64-*-* } } */
> +
> +void test_function(int x) {
> + /* Function should get both KCFI preamble and patchable entries. */
> +}
> +
> +int main() {
> + test_function(42);
> + return 0;
> +}
> +
> +/* Should have KCFI preamble. */
> +/* { dg-final { scan-assembler "__cfi_test_function:" } } */
> +
> +/* Should have patchable function entry section. */
> +/* { dg-final { scan-assembler "__patchable_function_entries" } } */
> +
> +/* x86_64: Should have exactly 2 prefix NOPs between .LPFE and .type. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*\.type} { target x86_64-*-* } } } */
> +
> +/* x86_64: Should have exactly 3 entry NOPs between .cfi_startproc and
> + pushq. */
> +/* { dg-final { scan-assembler {\.cfi_startproc\n\t*nop\n\t*nop\n\t*nop\n\t*pushq} { target x86_64-*-* } } } */
> +
> +/* x86_64: KCFI should have exactly 9 NOPs between __cfi_ and movl. */
> +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*movl} { target x86_64-*-* } } } */
> +
> +/* x86_64: Validate KCFI type ID is present. */
> +/* { dg-final { scan-assembler {movl\t\$0x[0-9a-f]+, %eax} { target x86_64-*-* } } } */
> +
> +/* AArch64: Should have exactly 2 prefix NOPs between .LPFE and .type. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*\.type} { target aarch64*-*-* } } } */
> +
> +/* AArch64: Should have exactly 3 entry NOPs between .cfi_startproc and
> + stack manipulation. */
> +/* { dg-final { scan-assembler {\.cfi_startproc\n\t*nop\n\t*nop\n\t*nop\n\t*sub\t*sp} { target aarch64*-*-* } } } */
> +
> +/* AArch64: KCFI should have alignment NOPs then .word immediate. */
> +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*nop\n\t*\.word\t0x[0-9a-f]+} { target aarch64*-*-* } } } */
> +
> +/* AArch64: Validate clean KCFI boundary - .word then immediate end/size. */
> +/* { dg-final { scan-assembler {\.word\t0x[0-9a-f]+\n\.Lcfi_func_end_test_function:\n\t\.size\t__cfi_test_function, \.-__cfi_test_function} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit: Should have exactly 2 prefix NOPs between .LPFE and .syntax. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*\.syntax} { target arm32 } } } */
> +
> +/* ARM 32-bit: Should have exactly 3 entry NOPs after function label. */
> +/* { dg-final { scan-assembler {test_function:\n\t*nop\n\t*nop\n\t*nop} { target arm32 } } } */
> +
> +/* ARM 32-bit: KCFI should have alignment NOPs then .word immediate. */
> +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*nop\n\t*\.word\t0x[0-9a-f]+} { target arm32 } } } */
> +
> +/* ARM 32-bit: Validate clean KCFI boundary - .word then immediate end/size. */
> +/* { dg-final { scan-assembler {\.word\t0x[0-9a-f]+\n\.Lcfi_func_end_test_function:\n\t\.size\t__cfi_test_function, \.-__cfi_test_function} { target arm32 } } } */
> +
> +/* RISC-V: Should have exactly 2 prefix NOPs between .LPFE and .type. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*\.type} { target riscv*-*-* } } } */
> +
> +/* RISC-V: Should have exactly 3 entry NOPs before .cfi_startproc followed
> + by addi sp. */
> +/* { dg-final { scan-assembler {nop\n\t*nop\n\t*nop\n\.LFB[0-9]+:\n\t*\.cfi_startproc\n\t*addi\t*sp} { target riscv*-*-* } } } */
> +
> +/* RISC-V: KCFI should have alignment NOPs then .word immediate. */
> +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*nop\n\t*\.word\t0x[0-9a-f]+} { target riscv*-*-* } } } */
> +
> +/* RISC-V: Validate clean KCFI boundary - .word then immediate end/size. */
> +/* { dg-final { scan-assembler {\.word\t0x[0-9a-f]+\n\.Lcfi_func_end_test_function:\n\t\.size\t__cfi_test_function, \.-__cfi_test_function} { target riscv*-*-* } } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-entry-only.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-entry-only.c
> new file mode 100644
> index 000000000000..62e1926e107e
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-entry-only.c
> @@ -0,0 +1,62 @@
> +/* Test KCFI with patchable function entries - entry NOPs only. */
> +/* { dg-do compile } */
> +/* { dg-additional-options "-fpatchable-function-entry=4,0" } */
> +/* { dg-additional-options "-falign-functions=16" { target x86_64-*-* } } */
> +
> +void test_function(void) {
> +}
> +
> +static void caller(void) {
> + /* Make an indirect call to test callsite offset calculation. */
> + void (*func_ptr)(void) = test_function;
> + func_ptr();
> +}
> +
> +int main() {
> + test_function(); /* Direct call. */
> + caller(); /* Indirect call via static function. */
> + return 0;
> +}
> +
> +/* x86_64: Should have KCFI preamble with architecture alignment NOPs (11). */
> +/* { dg-final { scan-assembler {__cfi_test_function:\n\t+nop\n\t+nop\n\t+nop\n\t+nop\n\t+nop\n\t+nop\n\t+nop\n\t+nop\n\t+nop\n\t+nop\n\t+nop\n\t+movl\t+\$0x[0-9a-f]+, %eax} { target x86_64-*-* } } } */
> +
> +/* AArch64: Should have KCFI preamble with no alignment NOPs. */
> +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*\.word\t0x[0-9a-f]+} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit: Should have KCFI preamble with no alignment NOPs. */
> +/* { dg-final { scan-assembler {__cfi_test_function:\n\t\.word\t0x[0-9a-f]+} { target arm32 } } } */
> +
> +/* RISC-V: Should have KCFI preamble with no alignment NOPs. */
> +/* { dg-final { scan-assembler {__cfi_test_function:\n\t\.word\t0x[0-9a-f]+} { target riscv*-*-* } } } */
> +
> +/* x86_64: Indirect call should use original prefix NOPs (0) for offset
> + calculation: -4 offset. */
> +/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r10d\n\taddl\t-4\(%r[a-z0-9]+\), %r10d\n\tje\t(\.Lkcfi_call[0-9]+)\n\.Lkcfi_trap[0-9]+:\n\tud2\n.*\n\1:\n\tcall} { target x86_64-*-* } } } */
> +
> +/* x86_64: All 4 NOPs are entry NOPs - should have exactly 4 entry NOPs. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*pushq} { target x86_64-*-* } } } */
> +
> +/* AArch64: All 4 NOPs are entry NOPs - should have exactly 4 entry NOPs. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*stp} { target aarch64*-*-* } } } */
> +
> +/* AArch64: No alignment NOPs - function type should come immediately before
> + function. */
> +/* { dg-final { scan-assembler {\.type\t*test_function, %function\n*test_function:} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit: All 4 NOPs are entry NOPs - should have exactly 4 entry NOPs. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop} { target arm32 } } } */
> +
> +/* ARM 32-bit: No alignment NOPs - function type should come immediately
> + before function. */
> +/* { dg-final { scan-assembler {\.type\t*test_function, %function\n*test_function:} { target arm32 } } } */
> +
> +/* RISC-V: All 4 NOPs are entry NOPs. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\.LFB} { target riscv*-*-* } } } */
> +
> +/* RISC-V: No alignment NOPs - function type should come immediately
> + before function. */
> +/* { dg-final { scan-assembler {\.type\t*test_function, @function\n*test_function:} { target riscv*-*-* } } } */
> +
> +/* Should have patchable function entry section. */
> +/* { dg-final { scan-assembler "__patchable_function_entries" } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-large.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-large.c
> new file mode 100644
> index 000000000000..3d5618847840
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-large.c
> @@ -0,0 +1,51 @@
> +/* Test KCFI with large patchable function entries. */
> +/* { dg-do compile } */
> +/* { dg-additional-options "-fpatchable-function-entry=11,11" } */
> +/* { dg-additional-options "-falign-functions=16" { target x86_64-*-* } } */
> +
> +void test_function(void) {
> +}
> +
> +int main() {
> + void (*func_ptr)(void) = test_function;
> + func_ptr();
> + return 0;
> +}
> +
> +/* Should have KCFI preamble. */
> +/* { dg-final { scan-assembler "__cfi_test_function:" } } */
> +
> +/* Should have patchable function entry section. */
> +/* { dg-final { scan-assembler "__patchable_function_entries" } } */
> +
> +/* x86_64: Should have exactly 11 alignment NOPs between .LPFE and .type. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type} { target x86_64-*-* } } } */
> +
> +/* x86_64: Should have 0 entry NOPs - function starts immediately with
> + pushq. */
> +/* { dg-final { scan-assembler {test_function:\n\.LFB[0-9]+:\n\t*\.cfi_startproc\n\t*pushq\t*%rbp} { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler-not {\t*\.weak\t*__kcfi_typeid_test_function\n} { target x86_64-*-* } } } */
> +
> +/* x86_64: KCFI should have 0 entry NOPs - goes directly to typeid movl. */
> +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*movl\t\$0x[0-9a-f]+, %eax} { target x86_64-*-* } } } */
> +
> +/* x86_64: Call site should use -15 offset. */
> +/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r10d\n\taddl\t-15\(%r[a-z0-9]+\), %r10d} { target x86_64-*-* } } } */
> +
> +/* AArch64: Should have exactly 11 prefix NOPs between .LPFE and .type. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit: Should have exactly 11 prefix NOPs between .LPFE and .type. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop} { target arm32 } } } */
> +
> +/* AArch64: Call site should use -15 offset. */
> +/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-15\]} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit: Call site should use -15 offset. */
> +/* { dg-final { scan-assembler {ldr\tr0, \[r[0-9]+, #-15\]} { target arm32 } } } */
> +
> +/* RISC-V: Should have 11 prefix NOPs between .LPFE and .type. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type} { target riscv*-*-* } } } */
> +
> +/* RISC-V: Call site should use -15 offset (same as x86/AArch64). */
> +/* { dg-final { scan-assembler {lw\tt1, -15\(} { target riscv*-*-* } } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-medium.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-medium.c
> new file mode 100644
> index 000000000000..4f00a86dbcb7
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-medium.c
> @@ -0,0 +1,60 @@
> +/* Test KCFI with medium patchable function entries. */
> +/* { dg-do compile } */
> +/* { dg-additional-options "-fpatchable-function-entry=8,4" } */
> +/* { dg-additional-options "-falign-functions=16" { target x86_64-*-* } } */
> +
> +void test_function(void) {
> +}
> +
> +int main() {
> + void (*func_ptr)(void) = test_function;
> + func_ptr();
> + return 0;
> +}
> +
> +/* Should have KCFI preamble. */
> +/* { dg-final { scan-assembler "__cfi_test_function:" } } */
> +
> +/* Should have patchable function entry section. */
> +/* { dg-final { scan-assembler "__patchable_function_entries" } } */
> +
> +/* x86_64: Should have exactly 4 prefix NOPs between .LPFE and .type. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type} { target x86_64-*-* } } } */
> +
> +/* x86_64: Should have exactly 4 entry NOPs between .cfi_startproc and
> + pushq. */
> +/* { dg-final { scan-assembler {\.cfi_startproc\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*pushq} { target x86_64-*-* } } } */
> +
> +/* x86_64: KCFI should have exactly 7 alignment NOPs between __cfi_ and
> + typeid movl. */
> +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*movl\t\$0x[0-9a-f]+, %eax} { target x86_64-*-* } } } */
> +
> +/* x86_64: Call site should use -8 offset. */
> +/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r10d\n\taddl\t-8\(%r[a-z0-9]+\), %r10d} { target x86_64-*-* } } } */
> +
> +/* AArch64: Should have exactly 4 prefix NOPs between .LPFE and .type. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type} { target aarch64*-*-* } } } */
> +
> +/* AArch64: Should have exactly 4 entry NOPs after .cfi_startproc. */
> +/* { dg-final { scan-assembler {\.cfi_startproc\n\t*nop\n\t*nop\n\t*nop\n\t*nop} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit: Should have exactly 4 prefix NOPs between .LPFE and .syntax. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.syntax} { target arm32 } } } */
> +
> +/* ARM 32-bit: Should have exactly 4 entry NOPs after function label. */
> +/* { dg-final { scan-assembler {test_function:\n\t*nop\n\t*nop\n\t*nop\n\t*nop} { target arm32 } } } */
> +
> +/* AArch64: Call site should use -8 offset. */
> +/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-8\]} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit: Call site should use -8 offset. */
> +/* { dg-final { scan-assembler {ldr\tr0, \[r[0-9]+, #-8\]} { target arm32 } } } */
> +
> +/* RISC-V: Should have exactly 4 prefix NOPs between .LPFE and .type. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type} { target riscv*-*-* } } } */
> +
> +/* RISC-V: Should have 4 entry NOPs. */
> +/* { dg-final { scan-assembler {test_function:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\.LFB} { target riscv*-*-* } } } */
> +
> +/* RISC-V: Call site should use -8 offset (same as x86/AArch64) */
> +/* { dg-final { scan-assembler {lw\tt1, -8\(} { target riscv*-*-* } } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-prefix-only.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-prefix-only.c
> new file mode 100644
> index 000000000000..98c53ef52989
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-prefix-only.c
> @@ -0,0 +1,60 @@
> +/* Test KCFI with patchable function entries - prefix NOPs only. */
> +/* { dg-do compile } */
> +/* { dg-additional-options "-fpatchable-function-entry=3,3" } */
> +/* { dg-additional-options "-falign-functions=16" { target x86_64-*-* } } */
> +
> +void test_function(void) {
> +}
> +
> +int main() {
> + test_function();
> + return 0;
> +}
> +
> +/* Should have KCFI preamble. */
> +/* { dg-final { scan-assembler "__cfi_test_function:" } } */
> +
> +/* x86_64: All 3 NOPs are prefix NOPs - should have exactly 3 prefix NOPs. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*\.type\t*test_function} { target x86_64-*-* } } } */
> +
> +/* x86_64: No entry NOPs - function should start immediately with prologue. */
> +/* { dg-final { scan-assembler {test_function:\n\.LFB[0-9]+:\n\t*\.cfi_startproc\n\t*pushq\t*%rbp} { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler-not {\t*\.weak\t*__kcfi_typeid_test_function\n} { target x86_64-*-* } } } */
> +
> +/* x86_64: should have exactly 8 alignment NOPs. */
> +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*movl} { target x86_64-*-* } } } */
> +
> +/* AArch64: All 3 NOPs are prefix NOPs - should have exactly 3 prefix NOPs. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*\.type\t*test_function} { target aarch64*-*-* } } } */
> +
> +/* AArch64: No entry NOPs - function should start immediately with prologue. */
> +/* { dg-final { scan-assembler {test_function:\n\.LFB[0-9]+:\n\t*\.cfi_startproc\n\t*nop\n\t*ret} { target aarch64*-*-* } } } */
> +/* { dg-final { scan-assembler-not {\t*\.weak\t*__kcfi_typeid_test_function\n} { target aarch64*-*-* } } } */
> +
> +/* AArch64: KCFI type ID should have 1 alignment NOP then word. */
> +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*\.word\t0x[0-9a-f]+} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit: All 3 NOPs are prefix NOPs - should have exactly 3 prefix NOPs. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop} { target arm32 } } } */
> +
> +/* ARM 32-bit: No entry NOPs - function should start immediately with
> + prologue. */
> +/* { dg-final { scan-assembler {test_function:} { target arm32 } } } */
> +/* { dg-final { scan-assembler-not {\t*\.weak\t*__kcfi_typeid_test_function\n} { target arm32 } } } */
> +
> +/* ARM 32-bit: KCFI type ID should have 1 alignment NOP then word. */
> +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*\.word\t0x[0-9a-f]+} { target arm32 } } } */
> +
> +/* RISC-V: All 3 NOPs are prefix NOPs - should have exactly 3 prefix NOPs. */
> +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*\.type\t*test_function} { target riscv*-*-* } } } */
> +
> +/* RISC-V: No entry NOPs - function should start immediately with
> + .cfi_startproc. */
> +/* { dg-final { scan-assembler {test_function:\n\.LFB[0-9]+:\n\t*\.cfi_startproc} { target riscv*-*-* } } } */
> +/* { dg-final { scan-assembler-not {\t*\.weak\t*__kcfi_typeid_test_function\n} { target riscv*-*-* } } } */
> +
> +/* RISC-V: KCFI type ID should have 1 alignment NOP then word. */
> +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*\.word\t0x[0-9a-f]+} { target riscv*-*-* } } } */
> +
> +/* Should have patchable function entry section. */
> +/* { dg-final { scan-assembler "__patchable_function_entries" } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-pic-addressing.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-pic-addressing.c
> new file mode 100644
> index 000000000000..26323db4572f
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-pic-addressing.c
> @@ -0,0 +1,104 @@
> +/* Test KCFI with position-independent code addressing modes.
> + This is a regression test for complex addressing like
> + PLUS(PLUS(...), symbol_ref) which can occur with PIC and caused
> + change_address_1 RTL errors. */
> +/* { dg-do compile } */
> +/* { dg-additional-options "-O2 -fpic" } */
> +
> +/* Global function pointer table that creates PIC addressing. */
> +struct callbacks {
> + int (*handler1)(int);
> + void (*handler2)(void);
> + int (*handler3)(int, int);
> +};
> +
> +static int simple_handler(int x) {
> + return x * 2;
> +}
> +
> +static void void_handler(void) {
> + /* Empty handler. */
> +}
> +
> +static int complex_handler(int a, int b) {
> + return a + b;
> +}
> +
> +/* Global structure that will require PIC addressing. */
> +struct callbacks global_callbacks = {
> + .handler1 = simple_handler,
> + .handler2 = void_handler,
> + .handler3 = complex_handler
> +};
> +
> +/* Function that uses PIC addressing to access global callbacks. */
> +int test_pic_addressing(int value) {
> + /* These indirect calls through global structure create complex
> + addressing like PLUS(PLUS(GOT_base, symbol_offset), struct_offset)
> + which previously caused RTL errors in KCFI instrumentation. */
> +
> + int result = 0;
> + result += global_callbacks.handler1(value);
> +
> + global_callbacks.handler2();
> +
> + result += global_callbacks.handler3(value, result);
> +
> + return result;
> +}
> +
> +/* Test with function pointer arrays. */
> +static int (*func_array[])(int) = {
> + simple_handler,
> + simple_handler,
> + simple_handler
> +};
> +
> +int test_pic_array(int index, int value) {
> + /* Array access with PIC can also create complex addressing. */
> + return func_array[index % 3](value);
> +}
> +
> +/* Test with dynamic PIC addressing. */
> +struct callbacks *get_callbacks(void) {
> + return &global_callbacks;
> +}
> +
> +int test_dynamic_pic(int value) {
> + /* Dynamic access through function call creates very complex addressing. */
> + struct callbacks *cb = get_callbacks();
> + return cb->handler1(value) + cb->handler3(value, value);
> +}
> +
> +int main() {
> + int result = 0;
> + result += test_pic_addressing(10);
> + result += test_pic_array(1, 20);
> + result += test_dynamic_pic(5);
> + return result;
> +}
> +
> +/* Verify that all address-taken functions get KCFI preambles. */
> +/* { dg-final { scan-assembler {__cfi_simple_handler:} } } */
> +/* { dg-final { scan-assembler {__cfi_void_handler:} } } */
> +/* { dg-final { scan-assembler {__cfi_complex_handler:} } } */
> +
> +/* x86_64: Verify KCFI checks are generated. */
> +/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r10d\n\taddl\t-4\(%r[a-z0-9]+\), %r10d} { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler {ud2} { target x86_64-*-* } } } */
> +
> +/* AArch64: Verify KCFI checks. */
> +/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-4\]} { target aarch64*-*-* } } } */
> +/* { dg-final { scan-assembler {brk} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit: Verify KCFI checks with PIC addressing and stack spilling. */
> +/* { dg-final { scan-assembler {ldr\tr0, \[r[0-9]+, #-4\]} { target arm32 } } } */
> +/* { dg-final { scan-assembler {udf} { target arm32 } } } */
> +
> +/* RISC-V: Verify KCFI checks are generated. */
> +/* { dg-final { scan-assembler {lw\tt1, -4\([a-z0-9]+\)\n\tlui\tt2, [0-9]+\n\taddiw\tt2, t2, -?[0-9]+} { target riscv*-*-* } } } */
> +/* { dg-final { scan-assembler {ebreak} { target riscv*-*-* } } } */
> +
> +/* Should have trap section. */
> +/* { dg-final { scan-assembler {\.kcfi_traps} { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler {\.kcfi_traps} { target riscv*-*-* } } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-retpoline-r11.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-retpoline-r11.c
> new file mode 100644
> index 000000000000..79e5ca61cdc2
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-retpoline-r11.c
> @@ -0,0 +1,50 @@
> +/* Test KCFI with retpoline thunk-extern flag forces r11 usage. */
> +/* { dg-do compile { target x86_64-*-* } } */
> +/* { dg-additional-options "-O2 -mindirect-branch=thunk-extern" } */
> +
> +extern int external_target(void);
> +
> +/* Test regular call (not tail call) */
> +__attribute__((noinline))
> +int call_test(int (*func_ptr)(void)) {
> + /* This indirect call should use r11 when both KCFI and
> + -mindirect-branch=thunk-extern are enabled. */
> + int result = func_ptr(); /* Function parameter prevents direct optimization. */
> + return result + 1; /* Prevent tail call optimization. */
> +}
> +
> +/* Reference external_target to generate the required symbol. */
> +int (*external_func_ptr)(void) = external_target;
> +
> +/* Test function for sibcalls (tail calls) */
> +__attribute__((noinline))
> +void sibcall_test(int (**func_ptr)(void)) {
> + /* This sibcall should use r11 when both KCFI and
> + -mindirect-branch=thunk-extern are enabled. */
> + (*func_ptr)(); /* Tail call - should be optimized to sibcall. */
> +}
> +
> +/* Should have weak symbol for external function. */
> +/* { dg-final { scan-assembler "__kcfi_typeid_external_target" } } */
> +
> +/* When both KCFI and -mindirect-branch=thunk-extern are enabled,
> + indirect calls should always use r11 register and convert to extern thunks. */
> +/* { dg-final { scan-assembler-times {call\s+__x86_indirect_thunk_r11} 1 } } */
> +
> +/* Sibcalls should also use r11 register and convert to extern thunks. */
> +/* { dg-final { scan-assembler-times {jmp\s+__x86_indirect_thunk_r11} 1 } } */
> +
> +/* Should have exactly 2 KCFI traps (one per function) */
> +/* { dg-final { scan-assembler-times {ud2} 2 } } */
> +
> +/* Should NOT use other registers for indirect calls. */
> +/* { dg-final { scan-assembler-not {call\s+\*%rax} } } */
> +/* { dg-final { scan-assembler-not {call\s+\*%rcx} } } */
> +/* { dg-final { scan-assembler-not {call\s+\*%rdx} } } */
> +/* { dg-final { scan-assembler-not {call\s+\*%rdi} } } */
> +
> +/* Should NOT use other registers for sibcalls. */
> +/* { dg-final { scan-assembler-not {jmp\s+\*%rax} } } */
> +/* { dg-final { scan-assembler-not {jmp\s+\*%rcx} } } */
> +/* { dg-final { scan-assembler-not {jmp\s+\*%rdx} } } */
> +/* { dg-final { scan-assembler-not {jmp\s+\*%rdi} } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-runtime.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-runtime.c
> new file mode 100644
> index 000000000000..6ad8fab5da80
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-runtime.c
> @@ -0,0 +1,151 @@
> +/* Test KCFI runtime behavior: working calls and type mismatch trapping.
> + { dg-do run { target native } }
> + { dg-options "-fsanitize=kcfi" } */
> +
> +#include <stdio.h>
> +#include <signal.h>
> +#include <setjmp.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +/* Test functions with different signatures */
> +static int func_int_void(void)
> +{
> + return 42;
> +}
> +
> +__attribute__((nocf_check))
> +static int func_int_void_nocf_check(void)
> +{
> + return 42;
> +}
> +
> +static int func_int_int(int x)
> +{
> + return x * 4;
> +}
> +
> +/* Global state for signal handling */
> +static volatile int trap_occurred = 0;
> +static jmp_buf trap_env;
> +
> +/* Signal handler for KCFI traps */
> +static void trap_handler(int sig)
> +{
> + trap_occurred = 1;
> + longjmp(trap_env, 1);
> +}
> +
> +/* Compatible indirect call should work */
> +static int test_compatible_call(void)
> +{
> + typedef int (*int_void_ptr)(void);
> + int_void_ptr ptr = func_int_void;
> +
> + fprintf(stderr, "Calling %s(0x%08x) through %s(0x%08x) ...\n",
> + __builtin_typeinfo_name(typeof(func_int_void)),
> + __builtin_typeinfo_hash(typeof(func_int_void)),
> + __builtin_typeinfo_name(typeof(*ptr)),
> + __builtin_typeinfo_hash(typeof(*ptr)));
> +
> + trap_occurred = 0;
> + /* This should work - same signature */
> + int result = ptr();
> +
> + return (trap_occurred == 0 && result == 42) ? 1 : 0;
> +}
> +
> +/* Compatible indirect call to nocf_check should not work */
> +static int test_nocf_check_trap(void)
> +{
> + trap_occurred = 0;
> +
> + if (setjmp(trap_env) == 0) {
> + typedef int (__attribute__((nocf_check)) *int_void_ptr_nocf)(void);
> + int_void_ptr_nocf ptr = func_int_void_nocf_check;
> +
> + fprintf(stderr, "Calling %s(0x%08x) through %s(0x%08x) ...\n",
> + __builtin_typeinfo_name(typeof(func_int_void_nocf_check)),
> + __builtin_typeinfo_hash(typeof(func_int_void_nocf_check)),
> + __builtin_typeinfo_name(typeof(*ptr)),
> + __builtin_typeinfo_hash(typeof(*ptr)));
> +
> + int result = ptr();
> +
> + /* If we get here, the trap didn't occur */
> + return 0;
> + } else {
> + /* We caught the trap - this is expected */
> + return trap_occurred;
> + }
> +}
> +
> +/* Type mismatch should trap */
> +static int test_type_mismatch_trap(void)
> +{
> + trap_occurred = 0;
> +
> + if (setjmp(trap_env) == 0) {
> + /* Cast func_int_void to incompatible void(*)(void) type */
> + typedef void (*void_void_ptr)(void);
> + void_void_ptr ptr = (void_void_ptr)func_int_void;
> +
> + fprintf(stderr, "Calling %s(0x%08x) through %s(0x%08x) ...\n",
> + __builtin_typeinfo_name(typeof(func_int_void)),
> + __builtin_typeinfo_hash(typeof(func_int_void)),
> + __builtin_typeinfo_name(typeof(*ptr)),
> + __builtin_typeinfo_hash(typeof(*ptr)));
> +
> + /* This should trap because type IDs don't match:
> + - func_int_void has type ID for int(void)
> + - but we're calling through void(void) pointer type */
> + ptr();
> +
> + /* If we get here, the trap didn't occur */
> + return 0;
> + } else {
> + /* We caught the trap - this is expected */
> + return trap_occurred;
> + }
> +}
> +
> +int main(void)
> +{
> + struct sigaction sa = {
> + .sa_handler = trap_handler,
> + .sa_flags = SA_NODEFER,
> + };
> + int failed = 3;
> +
> + /* Install trap handler. */
> + if (sigaction(SIGILL, &sa, NULL)) {
> + perror("sigaction");
> + return 1;
> + }
> +
> + /* Compatible call should work */
> + if (test_compatible_call()) {
> + printf("OK: matched indirect call succeeded\n");
> + failed--;
> + } else {
> + printf("FAIL\n");
> + }
> +
> + /* Using nocf_check should trap */
> + if (test_nocf_check_trap()) {
> + printf("OK: indirect call to nocf_check correctly trapped\n");
> + failed--;
> + } else {
> + printf("FAIL\n");
> + }
> +
> + /* Type mismatch should trap */
> + if (test_type_mismatch_trap()) {
> + printf("OK: mismatched indirect call correctly trapped\n");
> + failed--;
> + } else {
> + printf("FAIL\n");
> + }
> +
> + return failed;
> +}
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-tail-calls.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-tail-calls.c
> new file mode 100644
> index 000000000000..e2e3912fffa3
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-tail-calls.c
> @@ -0,0 +1,142 @@
> +/* Test KCFI protection when indirect calls get converted to tail calls. */
> +/* { dg-do compile } */
> +/* { dg-additional-options "-O2" } */
> +
> +typedef int (*func_ptr_t)(int);
> +typedef void (*void_func_ptr_t)(void);
> +
> +struct function_table {
> + func_ptr_t process;
> + void_func_ptr_t cleanup;
> +};
> +
> +/* Target functions. */
> +int process_data(int x) { return x * 2; }
> +void cleanup_data(void) {}
> +
> +/* Initialize function table. */
> +volatile struct function_table vtable = {
> + .process = &process_data,
> + .cleanup = &cleanup_data
> +};
> +
> +/* Indirect call through struct member that should become tail call. */
> +int test_struct_indirect_call(int x) {
> + /* This is an indirect call that should be converted to tail call:
> + Without -fno-optimize-sibling-calls should become "jmp *vtable+0(%rip)"
> + With -fno-optimize-sibling-calls should become "call *vtable+0(%rip)" */
> + return vtable.process(x);
> +}
> +
> +/* Indirect call through function pointer parameter. */
> +int test_param_indirect_call(func_ptr_t handler, int x) {
> + /* This is an indirect call that should be converted to tail call:
> + Without -fno-optimize-sibling-calls should become "jmp *%rdi"
> + With -fno-optimize-sibling-calls should be "call *%rdi" */
> + return handler(x);
> +}
> +
> +/* Void indirect call through struct member. */
> +void test_void_indirect_call(void) {
> + /* This is an indirect call that should be converted to tail call:
> + * Without -fno-optimize-sibling-calls: should become "jmp *vtable+8(%rip)"
> + * With -fno-optimize-sibling-calls: should be "call *vtable+8(%rip)" */
> + vtable.cleanup();
> +}
> +
> +/* Non-tail call for comparison (should always be call). */
> +int test_non_tail_indirect_call(func_ptr_t handler, int x) {
> + /* This should never become a tail call - always "call *%rdi" */
> + int result = handler(x);
> + return result + 1; /* Prevents tail call optimization. */
> +}
> +
> +/* Should have KCFI preambles for all functions. */
> +/* { dg-final { scan-assembler-times "__cfi_process_data:" 1 } } */
> +/* { dg-final { scan-assembler-times "__cfi_cleanup_data:" 1 } } */
> +/* { dg-final { scan-assembler-times "__cfi_test_struct_indirect_call:" 1 } } */
> +/* { dg-final { scan-assembler-times "__cfi_test_param_indirect_call:" 1 } } */
> +/* { dg-final { scan-assembler-times "__cfi_test_void_indirect_call:" 1 } } */
> +/* { dg-final { scan-assembler-times "__cfi_test_non_tail_indirect_call:" 1 } } */
> +
> +/* Should have exactly 4 KCFI checks for indirect calls as
> + (load type ID + compare). */
> +/* { dg-final { scan-assembler-times {movl\t\$-?[0-9]+, %r10d} 4 { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler-times {addl\t-4\(%r[a-z0-9]+\), %r10d} 4 { target x86_64-*-* } } } */
> +
> +/* Should have exactly 4 trap sections and 4 trap instructions. */
> +/* { dg-final { scan-assembler-times "\\.kcfi_traps" 4 { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler-times "ud2" 4 { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler-times "\\.kcfi_traps" 4 { target riscv*-*-* } } } */
> +/* { dg-final { scan-assembler-times "ebreak" 4 { target riscv*-*-* } } } */
> +
> +/* Should NOT have unprotected direct jumps to vtable. */
> +/* { dg-final { scan-assembler-not {jmp\t\*vtable\(%rip\)} { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler-not {jmp\t\*vtable\+8\(%rip\)} { target x86_64-*-* } } } */
> +
> +/* Should have exactly 3 protected tail calls (jmp through register after
> + KCFI check). */
> +/* { dg-final { scan-assembler-times {jmp\t\*%[a-z0-9]+} 3 { target x86_64-*-* } } } */
> +
> +/* Should have exactly 1 regular call (non-tail call case). */
> +/* { dg-final { scan-assembler-times {call\t\*%[a-z0-9]+} 1 { target x86_64-*-* } } } */
> +
> +/* RISC-V: Should have exactly 4 KCFI checks for indirect calls
> + (comparison instruction). */
> +/* { dg-final { scan-assembler-times {beq\tt1, t2, \.Lkcfi_call[0-9]+} 4 { target riscv*-*-* } } } */
> +
> +/* RISC-V: Should have exactly 4 KCFI checks for indirect calls as
> + (load type ID + compare). */
> +/* { dg-final { scan-assembler-times {lw\tt1, -4\([a-z0-9]+\)} 4 { target riscv*-*-* } } } */
> +/* { dg-final { scan-assembler-times {lui\tt2, [0-9]+} 4 { target riscv*-*-* } } } */
> +
> +/* RISC-V: Should have exactly 3 protected tail calls (jr after
> + KCFI check - no return address save). */
> +/* { dg-final { scan-assembler-times {jalr\t(x0|zero), [a-z0-9]+, 0} 3 { target riscv*-*-* } } } */
> +
> +/* RISC-V: Should have exactly 1 regular call (non-tail call case - saves
> + return address). */
> +/* { dg-final { scan-assembler-times {jalr\t(x1|ra), [a-z0-9]+, 0} 1 { target riscv*-*-* } } } */
> +
> +/* Type ID loading should use lui + addiw pattern for 32-bit constants. */
> +/* { dg-final { scan-assembler {lui\tt2, [0-9]+} { target riscv*-*-* } } } */
> +/* { dg-final { scan-assembler {addiw\tt2, t2, -?[0-9]+} { target riscv*-*-* } } } */
> +
> +/* Should have exactly 4 KCFI checks for indirect calls (load type ID from
> + -4 offset + compare). */
> +/* { dg-final { scan-assembler-times {ldur\tw16, \[x[0-9]+, #-4\]} 4 { target aarch64-*-* } } } */
> +/* { dg-final { scan-assembler-times {cmp\tw16, w17} 4 { target aarch64-*-* } } } */
> +
> +/* Should have exactly 4 trap instructions. */
> +/* { dg-final { scan-assembler-times {brk\t#[0-9]+} 4 { target aarch64-*-* } } } */
> +
> +/* Should have exactly 3 protected tail calls (br through register after
> + KCFI check). */
> +/* { dg-final { scan-assembler-times {br\tx[0-9]+} 3 { target aarch64-*-* } } } */
> +
> +/* Should have exactly 1 regular call (non-tail call case). */
> +/* { dg-final { scan-assembler-times {blr\tx[0-9]+} 1 { target aarch64-*-* } } } */
> +
> +/* Type ID loading should use mov + movk pattern for 32-bit constants. */
> +/* { dg-final { scan-assembler {mov\tw17, #[0-9]+} { target aarch64-*-* } } } */
> +/* { dg-final { scan-assembler {movk\tw17, #[0-9]+, lsl #16} { target aarch64-*-* } } } */
> +
> +/* Should have exactly 4 KCFI checks for indirect calls (load type ID from
> + -4 offset + compare). */
> +/* { dg-final { scan-assembler-times {ldr\tr0, \[r[0-9]+, #-4\]} 4 { target arm32 } } } */
> +/* { dg-final { scan-assembler-times {cmp\tr0, r1} 4 { target arm32 } } } */
> +
> +/* Should have exactly 4 trap instructions. */
> +/* { dg-final { scan-assembler-times {udf\t#[0-9]+} 4 { target arm32 } } } */
> +
> +/* Should have exactly 3 protected tail calls (bx through register after
> + KCFI check). */
> +/* { dg-final { scan-assembler-times {bx\tr[0-9]+} 3 { target arm32 } } } */
> +
> +/* Should have exactly 1 regular call (non-tail call case). */
> +/* { dg-final { scan-assembler-times {blx\tr[0-9]+} 1 { target arm32 } } } */
> +
> +/* Type ID loading should use movw + movt pattern for 32-bit constants
> + into r1. */
> +/* { dg-final { scan-assembler {movw\tr1, #[0-9]+} { target arm32 } } } */
> +/* { dg-final { scan-assembler {movt\tr1, #[0-9]+} { target arm32 } } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-trap-encoding.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-trap-encoding.c
> new file mode 100644
> index 000000000000..f2226fa58ac9
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-trap-encoding.c
> @@ -0,0 +1,54 @@
> +/* Test AArch64 and ARM32 KCFI trap encoding in BRK/UDF instructions. */
> +/* { dg-do compile { target { aarch64*-*-* || arm32 } } } */
> +
> +void target_function(int x, char y) {
> +}
> +
> +int main() {
> + void (*func_ptr)(int, char) = target_function;
> +
> + /* This should generate trap with immediate encoding. */
> + func_ptr(42, 'a');
> +
> + return 0;
> +}
> +
> +/* Should have KCFI preamble. */
> +/* { dg-final { scan-assembler "__cfi_target_function:" } } */
> +
> +/* AArch64 specific: Should have BRK instruction with proper ESR encoding
> + ESR format: 0x8000 | ((type_reg & 31) << 5) | (addr_reg & 31)
> +
> + Test the ESR encoding by checking for the expected value.
> + Since we know this test uses x2, we expect ESR = 0x8000 | (17<<5) | 2 = 33314
> +
> + A truly dynamic test would need to extract the register from blr and compute
> + the corresponding ESR, but DejaGnu's regex limitations make this complex.
> + This test validates the specific case and documents the encoding.
> + */
> +/* { dg-final { scan-assembler "blr\\s+x2" { target aarch64*-*-* } } } */
> +/* { dg-final { scan-assembler "brk\\s+#33314" { target aarch64*-*-* } } } */
> +
> +/* Should have KCFI check with type comparison. */
> +/* { dg-final { scan-assembler {ldur\t*w16, \[x[0-9]+, #-4\]} { target aarch64*-*-* } } } */
> +/* { dg-final { scan-assembler {cmp\t*w16, w17} { target aarch64*-*-* } } } */
> +
> +/* ARM32 specific: Should have UDF instruction with proper encoding
> + UDF format: 0x8000 | ((type_reg & 31) << 5) | (addr_reg & 31)
> +
> + Since ARM32 spills and restores r0/r1 before the trap, the type_reg
> + field uses 0x1F (31) to indicate "register was spilled" rather than
> + pointing to a live register. The addr_reg field contains the actual
> + target register number.
> +
> + For this test case using r3, we expect:
> + UDF = 0x8000 | (31 << 5) | 3 = 0x8000 | 0x3E0 | 3 = 33763
> + */
> +/* { dg-final { scan-assembler "blx\\s+r3" { target arm32 } } } */
> +/* { dg-final { scan-assembler "udf\\s+#33763" { target arm32 } } } */
> +
> +/* Should have register spilling and restoration around type check. */
> +/* { dg-final { scan-assembler {push\t*\{r0, r1\}} { target arm32 } } } */
> +/* { dg-final { scan-assembler {pop\t*\{r0, r1\}} { target arm32 } } } */
> +/* { dg-final { scan-assembler {ldr\t*r0, \[r[0-9]+, #-4\]} { target arm32 } } } */
> +/* { dg-final { scan-assembler {cmp\t*r0, r1} { target arm32 } } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-trap-section.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-trap-section.c
> new file mode 100644
> index 000000000000..7f5f8a82f3dc
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-trap-section.c
> @@ -0,0 +1,41 @@
> +/* Test KCFI trap section generation. */
> +/* { dg-do compile } */
> +
> +void target_function(void) {}
> +
> +int main() {
> + void (*func_ptr)(void) = target_function;
> +
> + /* Multiple indirect calls to generate multiple trap entries. */
> + func_ptr();
> + func_ptr();
> +
> + return 0;
> +}
> +
> +/* Should have KCFI preamble. */
> +/* { dg-final { scan-assembler "__cfi_target_function:" } } */
> +
> +/* Should have exactly 2 trap labels in code. */
> +/* { dg-final { scan-assembler-times {\.L[^:]+:\n\s*ud2} 2 { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler-times {\.L[^:]+:\n\s*brk} 2 { target aarch64*-*-* } } } */
> +/* { dg-final { scan-assembler-times {\.L[^:]+:\n\s*udf} 2 { target arm32 } } } */
> +/* { dg-final { scan-assembler-times {\.L[^:]+:\n\s*ebreak} 2 { target riscv*-*-* } } } */
> +
> +/* x86_64: Should have complete .kcfi_traps section sequence with relative
> + offset and 2 entries. */
> +/* { dg-final { scan-assembler {\.section\t\.kcfi_traps,"ao",@progbits,\.text\n\.Lkcfi_entry([^:]+):\n\t\.long\t\.Lkcfi_trap([^\s\n]+)-\.Lkcfi_entry\1\n\t\.text} { target x86_64-*-* } } } */
> +/* { dg-final { scan-assembler-times {\.section\t\.kcfi_traps,"ao",@progbits,\.text} 2 { target x86_64-*-* } } } */
> +
> +/* AArch64 should NOT have .kcfi_traps section (uses brk immediate instead) */
> +/* { dg-final { scan-assembler-not {\.section\t+\.kcfi_traps} { target aarch64*-*-* } } } */
> +/* { dg-final { scan-assembler-not {\.long.*-\.L} { target aarch64*-*-* } } } */
> +
> +/* ARM 32-bit should NOT have .kcfi_traps section (uses udf immediate instead) */
> +/* { dg-final { scan-assembler-not {\.section\t+\.kcfi_traps} { target arm32 } } } */
> +/* { dg-final { scan-assembler-not {\.long.*-\.L} { target arm32 } } } */
> +
> +/* RISC-V: Should have complete .kcfi_traps section sequence with relative
> + offset and 2 entries. */
> +/* { dg-final { scan-assembler {\.section\t\.kcfi_traps,"ao",@progbits,\.text\n\.Lkcfi_entry([^:]+):\n\t\.4byte\t\.L([^\s\n]+)-\.Lkcfi_entry\1\n\t\.text} { target riscv*-*-* } } } */
> +/* { dg-final { scan-assembler-times {\.section\t\.kcfi_traps,"ao",@progbits,\.text} 2 { target riscv*-*-* } } } */
> diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi.exp b/gcc/testsuite/gcc.dg/kcfi/kcfi.exp
> new file mode 100644
> index 000000000000..0bbba196c82f
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi.exp
> @@ -0,0 +1,64 @@
> +# Copyright (C) 2025 Free Software Foundation, Inc.
> +
> +# 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 GCC; see the file COPYING3. If not see
> +# <http://www.gnu.org/licenses/>.
> +
> +# GCC testsuite for KCFI (Kernel Control Flow Integrity) tests.
> +
> +# Load support procs.
> +load_lib gcc-dg.exp
> +
> +# KCFI is only supported on specific targets
> +if { ![istarget "x86_64-*-*"] \
> + && ![istarget "aarch64-*-*"] && ![istarget "arm*-*-*"] \
> + && ![istarget "riscv*-*-*"] } {
> + return
> +}
> +
> +# Skip tests if x86_64 is running in 32-bit mode (-m32)
> +if { [istarget "x86_64-*-*"] && ![check_effective_target_lp64] } {
> + return
> +}
> +
> +# Skip tests if AArch64 is running in ILP32 mode (-mabi=ilp32)
> +if { [istarget "aarch64-*-*"] && ![check_effective_target_lp64] } {
> + return
> +}
> +
> +# Skip tests if RISC-V is running in 32-bit mode (riscv32-*)
> +if { [istarget "riscv*-*-*"] && ![check_effective_target_lp64] } {
> + return
> +}
> +
> +# Add KCFI-specific flags to any existing DEFAULT_CFLAGS
> +global DEFAULT_CFLAGS
> +if ![info exists DEFAULT_CFLAGS] then {
> + set DEFAULT_CFLAGS ""
> +}
> +set DEFAULT_CFLAGS "$DEFAULT_CFLAGS -fsanitize=kcfi"
> +
> +# Add ARM32-specific flags for arm32 targets
> +if [check_effective_target_arm32] {
> + set DEFAULT_CFLAGS "$DEFAULT_CFLAGS -march=armv7-a -mfloat-abi=soft"
> +}
> +
> +# Initialize `dg'.
> +dg-init
> +
> +# Main loop.
> +dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.c]] \
> + "" $DEFAULT_CFLAGS
> +
> +# All done.
> +dg-finish
> --
> 2.34.1
>
Powered by blists - more mailing lists