On Sat, Sep 13, 2025 at 4:36 PM Kees Cook <k...@kernel.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 <k...@kernel.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 >