* [PATCH v2 01/20] coverage: move gcov and kcov under kernel/coverage/
2026-11-03 1:52 [PATCH v2 00/20] Unify code coverage and add Clang source-based coverage with MC/DC Sasha Levin
@ 2026-11-03 1:52 ` Sasha Levin
2026-11-05 17:38 ` Peter Oberparleiter
2026-11-13 0:45 ` Nathan Chancellor
2026-11-03 1:52 ` [PATCH v2 04/20] compiler_types.h: Don't instrument __always_inline functions for coverage Sasha Levin
` (8 subsequent siblings)
9 siblings, 2 replies; 21+ messages in thread
From: Sasha Levin @ 2026-11-03 1:52 UTC (permalink / raw)
To: linux-kernel
Cc: Alexander Potapenko, Andrew Morton, Andrey Konovalov, Ben Segall,
Bill Wendling, Chuck Wolber, Dietmar Eggemann, Dmitry Vyukov,
Ingo Molnar, Juri Lelli, Justin Stitt, kasan-dev, K Prateek Nayak,
linux-doc, llvm, Marco Elver, Masami Hiramatsu,
Mauro Carvalho Chehab, Mel Gorman, Nathan Chancellor,
Nick Desaulniers, Peter Oberparleiter, Peter Zijlstra,
Steven Rostedt, Thomas Gleixner, Valentin Schneider,
Vincent Guittot
The kernel carries more than one code coverage implementation, each
with its own directory, Kconfig menu, build plumbing and debugfs
interface. Start consolidating them by moving the existing
implementations next to each other:
kernel/gcov/ -> kernel/coverage/gcov/
kernel/kcov.c -> kernel/coverage/kcov.c
Both are configured from a new "Code coverage" menu in
kernel/coverage/Kconfig, which is sourced from "Kernel Testing and
Coverage" where the KCOV options used to live. The gcov options move
there from "General architecture-dependent options".
No functional change: the Kconfig symbols and their dependencies are
unchanged, and so are the per-object compiler flags of kcov.o, which
move to kernel/coverage/Makefile.
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
MAINTAINERS | 8 +++-
arch/Kconfig | 2 -
include/linux/sched.h | 2 +-
kernel/Makefile | 15 +------
kernel/coverage/Kconfig | 65 +++++++++++++++++++++++++++
kernel/coverage/Makefile | 16 +++++++
kernel/{ => coverage}/gcov/Kconfig | 0
kernel/{ => coverage}/gcov/Makefile | 0
kernel/{ => coverage}/gcov/base.c | 0
kernel/{ => coverage}/gcov/clang.c | 2 +-
kernel/{ => coverage}/gcov/fs.c | 0
kernel/{ => coverage}/gcov/gcc_4_7.c | 0
kernel/{ => coverage}/gcov/gcc_base.c | 0
kernel/{ => coverage}/gcov/gcov.h | 0
kernel/{ => coverage}/kcov.c | 0
lib/Kconfig.debug | 59 +-----------------------
tools/unittests/test_cmatch.py | 2 +-
17 files changed, 92 insertions(+), 79 deletions(-)
create mode 100644 kernel/coverage/Kconfig
create mode 100644 kernel/coverage/Makefile
rename kernel/{ => coverage}/gcov/Kconfig (100%)
rename kernel/{ => coverage}/gcov/Makefile (100%)
rename kernel/{ => coverage}/gcov/base.c (100%)
rename kernel/{ => coverage}/gcov/clang.c (99%)
rename kernel/{ => coverage}/gcov/fs.c (100%)
rename kernel/{ => coverage}/gcov/gcc_4_7.c (100%)
rename kernel/{ => coverage}/gcov/gcc_base.c (100%)
rename kernel/{ => coverage}/gcov/gcov.h (100%)
rename kernel/{ => coverage}/kcov.c (100%)
diff --git a/MAINTAINERS b/MAINTAINERS
index 93abbb315065d..79de9a6964152 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11173,7 +11173,9 @@ GCOV BASED KERNEL PROFILING
M: Peter Oberparleiter <oberpar@linux.ibm.com>
S: Maintained
F: Documentation/dev-tools/gcov.rst
-F: kernel/gcov/
+F: kernel/coverage/Kconfig
+F: kernel/coverage/Makefile
+F: kernel/coverage/gcov/
F: scripts/Makefile.gcov
GDB KERNEL DEBUGGING HELPER SCRIPTS
@@ -14395,7 +14397,9 @@ B: https://bugzilla.kernel.org/buglist.cgi?component=Sanitizers&product=Memory%2
F: Documentation/dev-tools/kcov.rst
F: include/linux/kcov.h
F: include/uapi/linux/kcov.h
-F: kernel/kcov.c
+F: kernel/coverage/Kconfig
+F: kernel/coverage/Makefile
+F: kernel/coverage/kcov.c
F: scripts/Makefile.kcov
KCSAN
diff --git a/arch/Kconfig b/arch/Kconfig
index 1163736de1f99..057ada4747387 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -1769,8 +1769,6 @@ config ARCH_VMLINUX_NEEDS_RELOCS
config HAVE_GENERIC_TIF_BITS
bool
-source "kernel/gcov/Kconfig"
-
source "scripts/gcc-plugins/Kconfig"
config FUNCTION_ALIGNMENT_4B
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 1c93da75226e3..aa71fe45a6ae1 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1536,7 +1536,7 @@ struct task_struct {
#endif /* CONFIG_TRACING */
#ifdef CONFIG_KCOV
- /* See kernel/kcov.c for more details. */
+ /* See kernel/coverage/kcov.c for more details. */
/* Coverage collection mode enabled for this task (0 if disabled): */
unsigned int kcov_mode;
diff --git a/kernel/Makefile b/kernel/Makefile
index 4ff92e24680c5..50bf37a26bae3 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -35,18 +35,6 @@ KCSAN_SANITIZE_softirq.o = n
# and produce insane amounts of uninteresting coverage.
KCOV_INSTRUMENT_extable.o := n
KCOV_INSTRUMENT_stacktrace.o := n
-# Don't self-instrument.
-KCOV_INSTRUMENT_kcov.o := n
-# If sanitizers detect any issues in kcov, it may lead to recursion
-# via printk, etc.
-KASAN_SANITIZE_kcov.o := n
-KCSAN_SANITIZE_kcov.o := n
-UBSAN_SANITIZE_kcov.o := n
-KMSAN_SANITIZE_kcov.o := n
-
-CONTEXT_ANALYSIS_kcov.o := y
-CFLAGS_kcov.o := $(call cc-option, -fno-conserve-stack) -fno-stack-protector
-
obj-y += sched/
obj-y += locking/
obj-y += power/
@@ -96,8 +84,7 @@ obj-$(CONFIG_IKHEADERS) += kheaders.o
obj-$(CONFIG_SMP) += stop_machine.o
obj-$(CONFIG_AUDIT) += audit.o auditfilter.o
obj-$(CONFIG_AUDITSYSCALL) += auditsc.o audit_watch.o audit_fsnotify.o audit_tree.o
-obj-$(CONFIG_GCOV_KERNEL) += gcov/
-obj-$(CONFIG_KCOV) += kcov.o
+obj-y += coverage/
obj-$(CONFIG_KPROBES) += kprobes.o
obj-$(CONFIG_FAIL_FUNCTION) += fail_function.o
obj-$(CONFIG_KGDB) += debug/
diff --git a/kernel/coverage/Kconfig b/kernel/coverage/Kconfig
new file mode 100644
index 0000000000000..dd90b6a61ae65
--- /dev/null
+++ b/kernel/coverage/Kconfig
@@ -0,0 +1,65 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menu "Code coverage"
+
+source "kernel/coverage/gcov/Kconfig"
+
+config ARCH_HAS_KCOV
+ bool
+ help
+ An architecture should select this when it can successfully
+ build and run with CONFIG_KCOV. This typically requires
+ disabling instrumentation for some early boot code.
+
+config KCOV
+ bool "Code coverage for fuzzing"
+ depends on ARCH_HAS_KCOV
+ depends on !ARCH_WANTS_NO_INSTR || HAVE_NOINSTR_HACK || \
+ GCC_VERSION >= 120000 || CC_IS_CLANG
+ select DEBUG_FS
+ select OBJTOOL if HAVE_NOINSTR_HACK
+ help
+ KCOV exposes kernel code coverage information in a form suitable
+ for coverage-guided fuzzing (randomized testing).
+
+ For more details, see Documentation/dev-tools/kcov.rst.
+
+config KCOV_ENABLE_COMPARISONS
+ bool "Enable comparison operands collection by KCOV"
+ depends on KCOV
+ depends on $(cc-option,-fsanitize-coverage=trace-cmp)
+ help
+ KCOV also exposes operands of every comparison in the instrumented
+ code along with operand sizes and PCs of the comparison instructions.
+ These operands can be used by fuzzing engines to improve the quality
+ of fuzzing coverage.
+
+config KCOV_INSTRUMENT_ALL
+ bool "Instrument all code by default"
+ depends on KCOV
+ default y
+ help
+ If you are doing generic system call fuzzing (like e.g. syzkaller),
+ then you will want to instrument the whole kernel and you should
+ say y here. If you are doing more targeted fuzzing (like e.g.
+ filesystem fuzzing with AFL) then you will want to enable coverage
+ for more specific subsets of files, and should say n here.
+
+config KCOV_IRQ_AREA_SIZE
+ hex "Size of interrupt coverage collection area in words"
+ depends on KCOV
+ range 0x80 0x1000000
+ default 0x40000
+ help
+ KCOV uses preallocated areas to collect coverage from soft
+ interrupts. This specifies the size of those areas in the
+ number of unsigned long words.
+
+config KCOV_SELFTEST
+ bool "Perform short selftests on boot"
+ depends on KCOV
+ help
+ Run short KCOV coverage collection selftests on boot.
+ On test failure, causes the kernel to panic. Recommended to be
+ enabled, ensuring critical functionality works as intended.
+
+endmenu
diff --git a/kernel/coverage/Makefile b/kernel/coverage/Makefile
new file mode 100644
index 0000000000000..a4cd8cc18d9bc
--- /dev/null
+++ b/kernel/coverage/Makefile
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_GCOV_KERNEL) += gcov/
+obj-$(CONFIG_KCOV) += kcov.o
+
+# Don't self-instrument.
+KCOV_INSTRUMENT_kcov.o := n
+# If sanitizers detect any issues in kcov, it may lead to recursion
+# via printk, etc.
+KASAN_SANITIZE_kcov.o := n
+KCSAN_SANITIZE_kcov.o := n
+UBSAN_SANITIZE_kcov.o := n
+KMSAN_SANITIZE_kcov.o := n
+
+CONTEXT_ANALYSIS_kcov.o := y
+CFLAGS_kcov.o := $(call cc-option, -fno-conserve-stack) -fno-stack-protector
diff --git a/kernel/gcov/Kconfig b/kernel/coverage/gcov/Kconfig
similarity index 100%
rename from kernel/gcov/Kconfig
rename to kernel/coverage/gcov/Kconfig
diff --git a/kernel/gcov/Makefile b/kernel/coverage/gcov/Makefile
similarity index 100%
rename from kernel/gcov/Makefile
rename to kernel/coverage/gcov/Makefile
diff --git a/kernel/gcov/base.c b/kernel/coverage/gcov/base.c
similarity index 100%
rename from kernel/gcov/base.c
rename to kernel/coverage/gcov/base.c
diff --git a/kernel/gcov/clang.c b/kernel/coverage/gcov/clang.c
similarity index 99%
rename from kernel/gcov/clang.c
rename to kernel/coverage/gcov/clang.c
index fd98ced0e51d2..e979e8983dce8 100644
--- a/kernel/gcov/clang.c
+++ b/kernel/coverage/gcov/clang.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019 Google, Inc.
- * modified from kernel/gcov/gcc_4_7.c
+ * modified from kernel/coverage/gcov/gcc_4_7.c
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
diff --git a/kernel/gcov/fs.c b/kernel/coverage/gcov/fs.c
similarity index 100%
rename from kernel/gcov/fs.c
rename to kernel/coverage/gcov/fs.c
diff --git a/kernel/gcov/gcc_4_7.c b/kernel/coverage/gcov/gcc_4_7.c
similarity index 100%
rename from kernel/gcov/gcc_4_7.c
rename to kernel/coverage/gcov/gcc_4_7.c
diff --git a/kernel/gcov/gcc_base.c b/kernel/coverage/gcov/gcc_base.c
similarity index 100%
rename from kernel/gcov/gcc_base.c
rename to kernel/coverage/gcov/gcc_base.c
diff --git a/kernel/gcov/gcov.h b/kernel/coverage/gcov/gcov.h
similarity index 100%
rename from kernel/gcov/gcov.h
rename to kernel/coverage/gcov/gcov.h
diff --git a/kernel/kcov.c b/kernel/coverage/kcov.c
similarity index 100%
rename from kernel/kcov.c
rename to kernel/coverage/kcov.c
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 2d22638ec13a6..bb939219f4cb4 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2192,64 +2192,7 @@ config FAULT_INJECTION_STACKTRACE_FILTER
help
Provide stacktrace filter for fault-injection capabilities
-config ARCH_HAS_KCOV
- bool
- help
- An architecture should select this when it can successfully
- build and run with CONFIG_KCOV. This typically requires
- disabling instrumentation for some early boot code.
-
-config KCOV
- bool "Code coverage for fuzzing"
- depends on ARCH_HAS_KCOV
- depends on !ARCH_WANTS_NO_INSTR || HAVE_NOINSTR_HACK || \
- GCC_VERSION >= 120000 || CC_IS_CLANG
- select DEBUG_FS
- select OBJTOOL if HAVE_NOINSTR_HACK
- help
- KCOV exposes kernel code coverage information in a form suitable
- for coverage-guided fuzzing (randomized testing).
-
- For more details, see Documentation/dev-tools/kcov.rst.
-
-config KCOV_ENABLE_COMPARISONS
- bool "Enable comparison operands collection by KCOV"
- depends on KCOV
- depends on $(cc-option,-fsanitize-coverage=trace-cmp)
- help
- KCOV also exposes operands of every comparison in the instrumented
- code along with operand sizes and PCs of the comparison instructions.
- These operands can be used by fuzzing engines to improve the quality
- of fuzzing coverage.
-
-config KCOV_INSTRUMENT_ALL
- bool "Instrument all code by default"
- depends on KCOV
- default y
- help
- If you are doing generic system call fuzzing (like e.g. syzkaller),
- then you will want to instrument the whole kernel and you should
- say y here. If you are doing more targeted fuzzing (like e.g.
- filesystem fuzzing with AFL) then you will want to enable coverage
- for more specific subsets of files, and should say n here.
-
-config KCOV_IRQ_AREA_SIZE
- hex "Size of interrupt coverage collection area in words"
- depends on KCOV
- range 0x80 0x1000000
- default 0x40000
- help
- KCOV uses preallocated areas to collect coverage from soft
- interrupts. This specifies the size of those areas in the
- number of unsigned long words.
-
-config KCOV_SELFTEST
- bool "Perform short selftests on boot"
- depends on KCOV
- help
- Run short KCOV coverage collection selftests on boot.
- On test failure, causes the kernel to panic. Recommended to be
- enabled, ensuring critical functionality works as intended.
+source "kernel/coverage/Kconfig"
menuconfig RUNTIME_TESTING_MENU
bool "Runtime Testing"
diff --git a/tools/unittests/test_cmatch.py b/tools/unittests/test_cmatch.py
index 109141cd2ab8d..06f9c61572e2e 100755
--- a/tools/unittests/test_cmatch.py
+++ b/tools/unittests/test_cmatch.py
@@ -706,7 +706,7 @@ class TestSubWithLocalXforms(TestCaseDiff):
def test_struct_kcov(self):
"""
- Test a struct from kernel/kcov.c.
+ Test a struct from kernel/coverage/kcov.c.
"""
line = """
struct kcov {
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH v2 01/20] coverage: move gcov and kcov under kernel/coverage/
2026-11-03 1:52 ` [PATCH v2 01/20] coverage: move gcov and kcov under kernel/coverage/ Sasha Levin
@ 2026-11-05 17:38 ` Peter Oberparleiter
2026-11-13 0:45 ` Nathan Chancellor
1 sibling, 0 replies; 21+ messages in thread
From: Peter Oberparleiter @ 2026-11-05 17:38 UTC (permalink / raw)
To: Sasha Levin, linux-kernel
Cc: Alexander Potapenko, Andrew Morton, Andrey Konovalov, Ben Segall,
Bill Wendling, Chuck Wolber, Dietmar Eggemann, Dmitry Vyukov,
Ingo Molnar, Juri Lelli, Justin Stitt, kasan-dev, K Prateek Nayak,
linux-doc, llvm, Marco Elver, Masami Hiramatsu,
Mauro Carvalho Chehab, Mel Gorman, Nathan Chancellor,
Nick Desaulniers, Peter Zijlstra, Steven Rostedt, Thomas Gleixner,
Valentin Schneider, Vincent Guittot
On 03.11.2026 02:52, Sasha Levin wrote:
> The kernel carries more than one code coverage implementation, each
> with its own directory, Kconfig menu, build plumbing and debugfs
> interface. Start consolidating them by moving the existing
> implementations next to each other:
>
> kernel/gcov/ -> kernel/coverage/gcov/
> kernel/kcov.c -> kernel/coverage/kcov.c
>
> Both are configured from a new "Code coverage" menu in
> kernel/coverage/Kconfig, which is sourced from "Kernel Testing and
> Coverage" where the KCOV options used to live. The gcov options move
> there from "General architecture-dependent options".
>
> No functional change: the Kconfig symbols and their dependencies are
> unchanged, and so are the per-object compiler flags of kcov.o, which
> move to kernel/coverage/Makefile.
>
> Signed-off-by: Sasha Levin <sashal@kernel.org>
For the gcov portion:
Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com>
--
Peter Oberparleiter
Linux on IBM Z Development - IBM Germany R&D
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 01/20] coverage: move gcov and kcov under kernel/coverage/
2026-11-03 1:52 ` [PATCH v2 01/20] coverage: move gcov and kcov under kernel/coverage/ Sasha Levin
2026-11-05 17:38 ` Peter Oberparleiter
@ 2026-11-13 0:45 ` Nathan Chancellor
1 sibling, 0 replies; 21+ messages in thread
From: Nathan Chancellor @ 2026-11-13 0:45 UTC (permalink / raw)
To: Sasha Levin
Cc: linux-kernel, Alexander Potapenko, Andrew Morton,
Andrey Konovalov, Ben Segall, Bill Wendling, Chuck Wolber,
Dietmar Eggemann, Dmitry Vyukov, Ingo Molnar, Juri Lelli,
Justin Stitt, kasan-dev, K Prateek Nayak, linux-doc, llvm,
Marco Elver, Masami Hiramatsu, Mauro Carvalho Chehab, Mel Gorman,
Nathan Chancellor, Nick Desaulniers, Peter Oberparleiter,
Peter Zijlstra, Steven Rostedt, Thomas Gleixner,
Valentin Schneider, Vincent Guittot
On Mon, 02 Nov 2026 20:52:19 -0500, Sasha Levin <sashal@kernel.org> wrote:
> The kernel carries more than one code coverage implementation, each
> with its own directory, Kconfig menu, build plumbing and debugfs
> interface. Start consolidating them by moving the existing
> implementations next to each other:
>
> kernel/gcov/ -> kernel/coverage/gcov/
> kernel/kcov.c -> kernel/coverage/kcov.c
>
> [...]
Reviewed-by: Nathan Chancellor <nathan@kernel.org>
--
Cheers,
Nathan
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v2 04/20] compiler_types.h: Don't instrument __always_inline functions for coverage
2026-11-03 1:52 [PATCH v2 00/20] Unify code coverage and add Clang source-based coverage with MC/DC Sasha Levin
2026-11-03 1:52 ` [PATCH v2 01/20] coverage: move gcov and kcov under kernel/coverage/ Sasha Levin
@ 2026-11-03 1:52 ` Sasha Levin
2026-11-03 1:52 ` [PATCH v2 11/20] objtool: Detect code coverage instrumentation in noinstr code Sasha Levin
` (7 subsequent siblings)
9 siblings, 0 replies; 21+ messages in thread
From: Sasha Levin @ 2026-11-03 1:52 UTC (permalink / raw)
To: linux-kernel
Cc: Bill Wendling, Chuck Wolber, Justin Stitt, llvm, Marco Elver,
Masami Hiramatsu, Miguel Ojeda, Nathan Chancellor,
Nick Desaulniers, Peter Zijlstra, Thomas Gleixner
noinstr code may only call other noinstr functions and __always_inline
functions. gcov and Clang's source-based code coverage insert their
instrumentation before inlining, so the instrumentation of an
__always_inline function ends up in every noinstr function it gets
inlined into. With Clang's source-based coverage on x86, 2500 of the
3449 relocations in .noinstr.text and 333 of the 451 in .cpuidle.text
of vmlinux.o point to coverage counters or MC/DC bitmaps. GCC's gcov
does the same. Since these are plain memory updates rather than calls,
objtool's noinstr validation doesn't notice them.
KCOV doesn't have this problem, as it instruments the code after
inlining and checks the attributes of the function that the code ends
up in.
Make __always_inline imply __no_profile when a code coverage
measurement implementation is enabled, so that no noinstr code carries
coverage instrumentation. The cost is that __always_inline functions
don't show up in the coverage data at all, including at their callers
outside of noinstr code. For an x86_64 configuration built with Clang 22
and MC/DC, that removes 556 of 60788 functions (0.9%), 29261 of 1598305
regions (1.8%), 11067 of 591108 branches (1.9%) and 445 of 36561 MC/DC
conditions (1.2%) from the coverage data.
This works around a limitation shared by both compilers: they lower
the coverage instrumentation before inlining, and let __always_inline
override a no_profile_instrument_function mismatch, so they can't drop
the instrumentation that ends up in a noinstr function. Once they can,
this can be limited to compilers that don't.
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
include/linux/compiler_types.h | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/include/linux/compiler_types.h b/include/linux/compiler_types.h
index c5921f1390079..5c8fff84004b4 100644
--- a/include/linux/compiler_types.h
+++ b/include/linux/compiler_types.h
@@ -246,6 +246,17 @@ struct ftrace_likely_data {
*/
#define __inline__ inline
+/*
+ * noinstr code may only call noinstr and __always_inline functions. Code
+ * coverage instrumentation is added before inlining, so the counters of an
+ * __always_inline function would end up in the noinstr code it gets inlined
+ * into: don't instrument __always_inline functions.
+ */
+#ifdef CONFIG_COVERAGE_KERNEL
+#undef __always_inline
+#define __always_inline inline __attribute__((__always_inline__)) __no_profile
+#endif
+
/*
* GCC does not warn about unused static inline functions for -Wunused-function.
* Suppress the warning in clang as well by using __maybe_unused, but enable it
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH v2 11/20] objtool: Detect code coverage instrumentation in noinstr code
2026-11-03 1:52 [PATCH v2 00/20] Unify code coverage and add Clang source-based coverage with MC/DC Sasha Levin
2026-11-03 1:52 ` [PATCH v2 01/20] coverage: move gcov and kcov under kernel/coverage/ Sasha Levin
2026-11-03 1:52 ` [PATCH v2 04/20] compiler_types.h: Don't instrument __always_inline functions for coverage Sasha Levin
@ 2026-11-03 1:52 ` Sasha Levin
2026-11-12 6:23 ` Josh Poimboeuf
2026-11-03 1:52 ` [PATCH v2 12/20] x86/sev: Stop excluding noinstr.o from code coverage Sasha Levin
` (6 subsequent siblings)
9 siblings, 1 reply; 21+ messages in thread
From: Sasha Levin @ 2026-11-03 1:52 UTC (permalink / raw)
To: linux-kernel
Cc: Bill Wendling, Chuck Wolber, Josh Poimboeuf, Justin Stitt, llvm,
Marco Elver, Masami Hiramatsu, Nathan Chancellor,
Nick Desaulniers, Peter Zijlstra, Thomas Gleixner
gcov and Clang's source-based code coverage update their counters
inline, rather than by calling into a runtime like KCOV does, so
coverage instrumentation in noinstr code goes unnoticed by the noinstr
validation of call destinations.
Flag instructions in noinstr code, outside of instrumentation_begin()
and instrumentation_end() sections, that reference coverage counters:
GCC's __gcov<N>.* counters (including the MC/DC counters), Clang's gcov
__llvm_gcov_ctr* counters, and the __llvm_prf_cnts and __llvm_prf_bits
sections of Clang's source-based code coverage.
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
tools/objtool/check.c | 60 ++++++++++++++++++++++++++++-
tools/objtool/include/objtool/elf.h | 1 +
2 files changed, 60 insertions(+), 1 deletion(-)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 931c5c412f15d..b0ea6e42c4fda 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -4,6 +4,7 @@
*/
#define _GNU_SOURCE /* memmem() */
+#include <ctype.h>
#include <fnmatch.h>
#include <string.h>
#include <stdlib.h>
@@ -2490,6 +2491,21 @@ static bool is_profiling_func(const char *name)
return false;
}
+/*
+ * Return true if sym is a code coverage counter: GCC's __gcov<N>.* and
+ * Clang's __llvm_gcov_ctr* gcov counters, or the counters and MC/DC bitmaps
+ * of Clang's source-based code coverage.
+ */
+static bool is_coverage_counter(struct symbol *sym)
+{
+ if (!strcmp(sym->sec->name, "__llvm_prf_cnts") ||
+ !strcmp(sym->sec->name, "__llvm_prf_bits"))
+ return true;
+
+ return (strstarts(sym->name, "__gcov") && isdigit((unsigned char)sym->name[6])) ||
+ strstarts(sym->name, "__llvm_gcov_ctr");
+}
+
static int classify_symbols(struct objtool_file *file)
{
struct symbol *func;
@@ -2499,6 +2515,9 @@ static int classify_symbols(struct objtool_file *file)
if (is_notype_sym(func) && strstarts(func->name, ".L"))
func->local_label = true;
+ if (is_coverage_counter(func))
+ func->coverage_counter = true;
+
if (!is_global_sym(func))
continue;
@@ -3508,6 +3527,39 @@ static inline bool noinstr_call_dest(struct objtool_file *file,
return false;
}
+/*
+ * Code coverage instrumentation (gcov, Clang's source-based coverage) updates
+ * its counters inline rather than through calls, so it can't be caught by
+ * checking the call destinations of noinstr code.
+ */
+static bool insn_updates_coverage(struct objtool_file *file,
+ struct instruction *insn)
+{
+ struct symbol *sym;
+ struct reloc *reloc;
+
+ for (reloc = insn_reloc(file, insn);
+ reloc;
+ reloc = find_reloc_by_dest_range(file->elf, insn->sec,
+ reloc_offset(reloc) + 1,
+ insn->offset + insn->len -
+ (reloc_offset(reloc) + 1))) {
+
+ sym = reloc->sym;
+ if (is_sec_sym(sym)) {
+ sym = find_symbol_containing(sym->sec,
+ arch_insn_adjusted_addend(insn, reloc));
+ if (!sym)
+ sym = reloc->sym;
+ }
+
+ if (sym->coverage_counter)
+ return true;
+ }
+
+ return false;
+}
+
static int validate_call(struct objtool_file *file,
struct instruction *insn,
struct insn_state *state)
@@ -3678,9 +3730,15 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
nr_insns_visited++;
}
- if (statep->noinstr)
+ if (statep->noinstr) {
statep->instr += insn->instr;
+ if (statep->instr <= 0 && insn_updates_coverage(file, insn)) {
+ WARN_INSN(insn, "coverage counter update in .noinstr.text section");
+ return 1;
+ }
+ }
+
if (insn->hint) {
if (insn->restore) {
struct instruction *save_insn, *i;
diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h
index a82517a76a0f6..202e77f26785a 100644
--- a/tools/objtool/include/objtool/elf.h
+++ b/tools/objtool/include/objtool/elf.h
@@ -84,6 +84,7 @@ struct symbol {
u8 return_thunk : 1;
u8 fentry : 1;
u8 profiling_func : 1;
+ u8 coverage_counter : 1;
u8 warned : 1;
u8 embedded_insn : 1;
u8 local_label : 1;
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH v2 11/20] objtool: Detect code coverage instrumentation in noinstr code
2026-11-03 1:52 ` [PATCH v2 11/20] objtool: Detect code coverage instrumentation in noinstr code Sasha Levin
@ 2026-11-12 6:23 ` Josh Poimboeuf
2026-11-12 13:09 ` Sasha Levin
0 siblings, 1 reply; 21+ messages in thread
From: Josh Poimboeuf @ 2026-11-12 6:23 UTC (permalink / raw)
To: Sasha Levin
Cc: linux-kernel, Bill Wendling, Chuck Wolber, Justin Stitt, llvm,
Marco Elver, Masami Hiramatsu, Nathan Chancellor,
Nick Desaulniers, Peter Zijlstra, Thomas Gleixner
On Mon, Nov 02, 2026 at 08:52:29PM -0500, Sasha Levin wrote:
> gcov and Clang's source-based code coverage update their counters
> inline, rather than by calling into a runtime like KCOV does, so
> coverage instrumentation in noinstr code goes unnoticed by the noinstr
> validation of call destinations.
>
> Flag instructions in noinstr code, outside of instrumentation_begin()
> and instrumentation_end() sections, that reference coverage counters:
> GCC's __gcov<N>.* counters (including the MC/DC counters), Clang's gcov
> __llvm_gcov_ctr* counters, and the __llvm_prf_cnts and __llvm_prf_bits
> sections of Clang's source-based code coverage.
What exactly is the problem with noinstr code incrementing counters
inline?
--
Josh
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 11/20] objtool: Detect code coverage instrumentation in noinstr code
2026-11-12 6:23 ` Josh Poimboeuf
@ 2026-11-12 13:09 ` Sasha Levin
2026-11-12 15:12 ` Peter Zijlstra
0 siblings, 1 reply; 21+ messages in thread
From: Sasha Levin @ 2026-11-12 13:09 UTC (permalink / raw)
To: Josh Poimboeuf
Cc: linux-kernel, Bill Wendling, Chuck Wolber, Justin Stitt, llvm,
Marco Elver, Masami Hiramatsu, Nathan Chancellor,
Nick Desaulniers, Peter Zijlstra, Thomas Gleixner
On Wed, Nov 11, 2026 at 10:23:41PM -0800, Josh Poimboeuf wrote:
>What exactly is the problem with noinstr code incrementing counters
>inline?
The counter is a memory write, and a memory write can trap. In noinstr
code that can happen before we're ready to take another exception.
With GCOV_PROFILE_ALL today, exc_debug_kernel() bumps a counter before
it clears DR7. Set a watchpoint on that counter and the next kernel #DB
never returns, it just keeps re-entering the handler. I tried it on
v7.4-rc1 in a VM and it hangs.
noinstr is already supposed to keep these out, it has had __no_profile
for years. But that doesn't cover what gets inlined into it, and nothing
checks. This patch adds the check.
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 11/20] objtool: Detect code coverage instrumentation in noinstr code
2026-11-12 13:09 ` Sasha Levin
@ 2026-11-12 15:12 ` Peter Zijlstra
2026-11-12 18:41 ` Josh Poimboeuf
0 siblings, 1 reply; 21+ messages in thread
From: Peter Zijlstra @ 2026-11-12 15:12 UTC (permalink / raw)
To: Sasha Levin
Cc: Josh Poimboeuf, linux-kernel, Bill Wendling, Chuck Wolber,
Justin Stitt, llvm, Marco Elver, Masami Hiramatsu,
Nathan Chancellor, Nick Desaulniers, Thomas Gleixner
On Thu, Nov 12, 2026 at 08:09:06AM -0500, Sasha Levin wrote:
> On Wed, Nov 11, 2026 at 10:23:41PM -0800, Josh Poimboeuf wrote:
> >What exactly is the problem with noinstr code incrementing counters
> >inline?
>
> The counter is a memory write, and a memory write can trap. In noinstr
> code that can happen before we're ready to take another exception.
>
> With GCOV_PROFILE_ALL today, exc_debug_kernel() bumps a counter before
> it clears DR7. Set a watchpoint on that counter and the next kernel #DB
> never returns, it just keeps re-entering the handler. I tried it on
> v7.4-rc1 in a VM and it hangs.
>
> noinstr is already supposed to keep these out, it has had __no_profile
> for years. But that doesn't cover what gets inlined into it, and nothing
> checks. This patch adds the check.
Right, strictly speaking this is a toolchain bug, but since we need to
build with existing tools we need to bodge it somewhat :/
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 11/20] objtool: Detect code coverage instrumentation in noinstr code
2026-11-12 15:12 ` Peter Zijlstra
@ 2026-11-12 18:41 ` Josh Poimboeuf
2026-11-12 18:51 ` Josh Poimboeuf
0 siblings, 1 reply; 21+ messages in thread
From: Josh Poimboeuf @ 2026-11-12 18:41 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Sasha Levin, linux-kernel, Bill Wendling, Chuck Wolber,
Justin Stitt, llvm, Marco Elver, Masami Hiramatsu,
Nathan Chancellor, Nick Desaulniers, Thomas Gleixner
On Thu, Nov 12, 2026 at 04:12:32PM +0100, Peter Zijlstra wrote:
> On Thu, Nov 12, 2026 at 08:09:06AM -0500, Sasha Levin wrote:
> > On Wed, Nov 11, 2026 at 10:23:41PM -0800, Josh Poimboeuf wrote:
> > >What exactly is the problem with noinstr code incrementing counters
> > >inline?
> >
> > The counter is a memory write, and a memory write can trap. In noinstr
> > code that can happen before we're ready to take another exception.
> >
> > With GCOV_PROFILE_ALL today, exc_debug_kernel() bumps a counter before
> > it clears DR7. Set a watchpoint on that counter and the next kernel #DB
> > never returns, it just keeps re-entering the handler. I tried it on
> > v7.4-rc1 in a VM and it hangs.
> >
> > noinstr is already supposed to keep these out, it has had __no_profile
> > for years. But that doesn't cover what gets inlined into it, and nothing
> > checks. This patch adds the check.
>
> Right, strictly speaking this is a toolchain bug, but since we need to
> build with existing tools we need to bodge it somewhat :/
I must be missing something, if somebody sets a watchpoint in
exc_debug_kernel(), don't they get to keep the pieces?
It's also not clear why --hack-noinstr can't be extended to either NOP
out the affected instructions, or change the address relocation to point
to a dummy variable that won't trap.
And the changelog needs all that background, as the justification is not
clear at all (to me).
--
Josh
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 11/20] objtool: Detect code coverage instrumentation in noinstr code
2026-11-12 18:41 ` Josh Poimboeuf
@ 2026-11-12 18:51 ` Josh Poimboeuf
0 siblings, 0 replies; 21+ messages in thread
From: Josh Poimboeuf @ 2026-11-12 18:51 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Sasha Levin, linux-kernel, Bill Wendling, Chuck Wolber,
Justin Stitt, llvm, Marco Elver, Masami Hiramatsu,
Nathan Chancellor, Nick Desaulniers, Thomas Gleixner
On Thu, Nov 12, 2026 at 10:41:46AM -0800, Josh Poimboeuf wrote:
> On Thu, Nov 12, 2026 at 04:12:32PM +0100, Peter Zijlstra wrote:
> > On Thu, Nov 12, 2026 at 08:09:06AM -0500, Sasha Levin wrote:
> > > On Wed, Nov 11, 2026 at 10:23:41PM -0800, Josh Poimboeuf wrote:
> > > >What exactly is the problem with noinstr code incrementing counters
> > > >inline?
> > >
> > > The counter is a memory write, and a memory write can trap. In noinstr
> > > code that can happen before we're ready to take another exception.
> > >
> > > With GCOV_PROFILE_ALL today, exc_debug_kernel() bumps a counter before
> > > it clears DR7. Set a watchpoint on that counter and the next kernel #DB
> > > never returns, it just keeps re-entering the handler. I tried it on
> > > v7.4-rc1 in a VM and it hangs.
> > >
> > > noinstr is already supposed to keep these out, it has had __no_profile
> > > for years. But that doesn't cover what gets inlined into it, and nothing
> > > checks. This patch adds the check.
> >
> > Right, strictly speaking this is a toolchain bug, but since we need to
> > build with existing tools we need to bodge it somewhat :/
>
> I must be missing something, if somebody sets a watchpoint in
> exc_debug_kernel(), don't they get to keep the pieces?
Or rather, sets a watchpoint on a counter specific to
exc_debug_kernel()... Still, why would somebody do that.
> It's also not clear why --hack-noinstr can't be extended to either NOP
> out the affected instructions, or change the address relocation to point
> to a dummy variable that won't trap.
>
> And the changelog needs all that background, as the justification is not
> clear at all (to me).
--
Josh
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v2 12/20] x86/sev: Stop excluding noinstr.o from code coverage
2026-11-03 1:52 [PATCH v2 00/20] Unify code coverage and add Clang source-based coverage with MC/DC Sasha Levin
` (2 preceding siblings ...)
2026-11-03 1:52 ` [PATCH v2 11/20] objtool: Detect code coverage instrumentation in noinstr code Sasha Levin
@ 2026-11-03 1:52 ` Sasha Levin
2026-11-03 1:52 ` [PATCH v2 14/20] coverage: add Clang's source-based " Sasha Levin
` (5 subsequent siblings)
9 siblings, 0 replies; 21+ messages in thread
From: Sasha Levin @ 2026-11-03 1:52 UTC (permalink / raw)
To: linux-kernel
Cc: Bill Wendling, Borislav Petkov, Chuck Wolber, Dave Hansen,
H. Peter Anvin, Ingo Molnar, Justin Stitt, llvm, Marco Elver,
Masami Hiramatsu, Nathan Chancellor, Nick Desaulniers,
Peter Zijlstra, Thomas Gleixner, x86
noinstr.o was excluded from gcov after Clang's gcov instrumentation
left calls to kasan_check_write() in __sev_es_nmi_complete(), and
because any coverage instrumentation would violate noinstr.
The GHCB accessors no longer call the KASAN and KCSAN checks, and the
noinstr attribute is now sufficient to keep coverage instrumentation
out of noinstr code, which objtool verifies. Drop the now redundant
exclusion and rely on the attributes, like the rest of the kernel's
noinstr code does.
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
arch/x86/coco/sev/Makefile | 2 --
1 file changed, 2 deletions(-)
diff --git a/arch/x86/coco/sev/Makefile b/arch/x86/coco/sev/Makefile
index be4785d766ec1..fb8ffedfc8b0c 100644
--- a/arch/x86/coco/sev/Makefile
+++ b/arch/x86/coco/sev/Makefile
@@ -8,5 +8,3 @@ UBSAN_SANITIZE_noinstr.o := n
# GCC may fail to respect __no_sanitize_address or __no_kcsan when inlining
KASAN_SANITIZE_noinstr.o := n
KCSAN_SANITIZE_noinstr.o := n
-
-COVERAGE_PROFILE_noinstr.o := n
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH v2 14/20] coverage: add Clang's source-based code coverage
2026-11-03 1:52 [PATCH v2 00/20] Unify code coverage and add Clang source-based coverage with MC/DC Sasha Levin
` (3 preceding siblings ...)
2026-11-03 1:52 ` [PATCH v2 12/20] x86/sev: Stop excluding noinstr.o from code coverage Sasha Levin
@ 2026-11-03 1:52 ` Sasha Levin
2026-11-03 1:52 ` [PATCH v2 15/20] x86: Enable " Sasha Levin
` (4 subsequent siblings)
9 siblings, 0 replies; 21+ messages in thread
From: Sasha Levin @ 2026-11-03 1:52 UTC (permalink / raw)
To: linux-kernel
Cc: Wentao Zhang, Alexander Potapenko, Andrey Konovalov,
Arnd Bergmann, Bill Wendling, Chuck Wolber, Dmitry Vyukov,
Justin Stitt, kasan-dev, linux-arch, linux-kbuild, llvm,
Marco Elver, Masami Hiramatsu, Nathan Chancellor,
Nick Desaulniers, Nicolas Schier, Peter Oberparleiter,
Peter Zijlstra, Thomas Gleixner
Add Clang's Source-based Code Coverage [1] as a second code coverage
measurement implementation, next to gcov.
gcov works at the object code level, which may better reflect actual
execution, but its measurements can lose source level precision when
compiler optimizations are enabled, as they always are for the kernel.
In the following example from drivers/firmware/dmi_scan.c, measured on
v6.11, gcov reports six branch outcomes for an expression with four
conditions:
5: 1068: if (s == e || *e != '/' || !month || month > 12) {
branch 0 taken 5 (fallthrough)
branch 1 taken 0
branch 2 taken 5 (fallthrough)
branch 3 taken 0
branch 4 taken 0 (fallthrough)
branch 5 taken 5
Clang's Source-based Code Coverage instruments the code in the compiler
front end, so the coverage data maps back to the source code exactly,
regardless of optimizations, and reports each condition:
1068| 5| if (s == e || *e != '/' || !month || month > 12) {
------------------
| Branch (1068:6): [True: 0, False: 5]
| Branch (1068:16): [True: 0, False: 5]
| Branch (1068:29): [True: 0, False: 5]
| Branch (1068:39): [True: 0, False: 5]
------------------
It is selected with CONFIG_LLVM_COV_KERNEL in the "Code coverage
measurement" choice, uses the common COVERAGE_PROFILE annotations,
noinstr handling and reset, and exports the raw profile of the kernel
image in /sys/kernel/debug/coverage/llvm/profraw. The raw profile
formats of Clang 19 to 22 (version 10) and of Clang 23 and later
(version 11) are supported. Counters are updated atomically, like gcov
does when the compiler allows it.
The data is exported from sections that the architecture's linker script
places with LLVM_COV_DATA, so only code built into the kernel image is
instrumented; modules are left alone.
This reuses the debugfs interface and raw profile serialization of
earlier work by Sami Tolvanen et al. [2], without the parts specific to
profile guided optimization.
Link: https://clang.llvm.org/docs/SourceBasedCodeCoverage.html [1]
Link: https://lore.kernel.org/lkml/20210407211704.367039-1-morbo@google.com/ [2]
Originally-by: Wentao Zhang <wentaoz5@illinois.edu>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
MAINTAINERS | 2 +
Makefile | 1 +
include/asm-generic/vmlinux.lds.h | 36 ++++++
kernel/coverage/Kconfig | 33 ++++-
kernel/coverage/Makefile | 1 +
kernel/coverage/llvm/Makefile | 4 +
kernel/coverage/llvm/fs.c | 201 ++++++++++++++++++++++++++++++
kernel/coverage/llvm/llvm-cov.h | 139 +++++++++++++++++++++
scripts/Makefile.lib | 7 +-
scripts/Makefile.llvm-cov | 6 +
10 files changed, 427 insertions(+), 3 deletions(-)
create mode 100644 kernel/coverage/llvm/Makefile
create mode 100644 kernel/coverage/llvm/fs.c
create mode 100644 kernel/coverage/llvm/llvm-cov.h
create mode 100644 scripts/Makefile.llvm-cov
diff --git a/MAINTAINERS b/MAINTAINERS
index 01bdf404bfde8..d4523a5fef33e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6639,6 +6639,8 @@ F: kernel/coverage/Kconfig
F: kernel/coverage/Makefile
F: kernel/coverage/core.c
F: kernel/coverage/coverage.h
+F: kernel/coverage/llvm/
+F: scripts/Makefile.llvm-cov
CODE OF CONDUCT
M: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
diff --git a/Makefile b/Makefile
index f895752d4b05f..5b2da7e8ab22d 100644
--- a/Makefile
+++ b/Makefile
@@ -1205,6 +1205,7 @@ include-$(CONFIG_KMSAN) += scripts/Makefile.kmsan
include-$(CONFIG_UBSAN) += scripts/Makefile.ubsan
include-$(CONFIG_KCOV) += scripts/Makefile.kcov
include-$(CONFIG_GCOV_KERNEL) += scripts/Makefile.gcov
+include-$(CONFIG_LLVM_COV_KERNEL) += scripts/Makefile.llvm-cov
include-$(CONFIG_RANDSTRUCT) += scripts/Makefile.randstruct
include-$(CONFIG_KSTACK_ERASE) += scripts/Makefile.kstack_erase
include-$(CONFIG_AUTOFDO_CLANG) += scripts/Makefile.autofdo
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 3eb732942d699..840899b604336 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -359,6 +359,42 @@
#define THERMAL_TABLE(name)
#endif
+#ifdef CONFIG_LLVM_COV_KERNEL
+#define LLVM_COV_DATA \
+ __llvm_prf_data : AT(ADDR(__llvm_prf_data) - LOAD_OFFSET) { \
+ BOUNDED_SECTION_POST_LABEL(__llvm_prf_data, \
+ __llvm_prf_data, \
+ _start, _end) \
+ } \
+ __llvm_prf_cnts : AT(ADDR(__llvm_prf_cnts) - LOAD_OFFSET) { \
+ BOUNDED_SECTION_POST_LABEL(__llvm_prf_cnts, \
+ __llvm_prf_cnts, \
+ _start, _end) \
+ } \
+ __llvm_prf_names : AT(ADDR(__llvm_prf_names) - LOAD_OFFSET) { \
+ BOUNDED_SECTION_POST_LABEL(__llvm_prf_names, \
+ __llvm_prf_names, \
+ _start, _end) \
+ } \
+ __llvm_prf_bits : AT(ADDR(__llvm_prf_bits) - LOAD_OFFSET) { \
+ BOUNDED_SECTION_POST_LABEL(__llvm_prf_bits, \
+ __llvm_prf_bits, \
+ _start, _end) \
+ } \
+ __llvm_covfun : AT(ADDR(__llvm_covfun) - LOAD_OFFSET) { \
+ BOUNDED_SECTION_POST_LABEL(__llvm_covfun, \
+ __llvm_covfun, \
+ _start, _end) \
+ } \
+ __llvm_covmap : AT(ADDR(__llvm_covmap) - LOAD_OFFSET) { \
+ BOUNDED_SECTION_POST_LABEL(__llvm_covmap, \
+ __llvm_covmap, \
+ _start, _end) \
+ }
+#else
+#define LLVM_COV_DATA
+#endif
+
#define KERNEL_DTB() \
STRUCT_ALIGN(); \
__dtb_start = .; \
diff --git a/kernel/coverage/Kconfig b/kernel/coverage/Kconfig
index 62a6fa94bd308..acfcce23e4c97 100644
--- a/kernel/coverage/Kconfig
+++ b/kernel/coverage/Kconfig
@@ -4,6 +4,15 @@ menu "Code coverage"
config ARCH_HAS_GCOV_PROFILE_ALL
def_bool n
+config ARCH_HAS_LLVM_COV
+ bool
+ help
+ An architecture should select this when its linker script places
+ the sections of Clang's source-based code coverage (LLVM_COV_DATA).
+
+config ARCH_HAS_LLVM_COV_PROFILE_ALL
+ bool
+
choice
prompt "Code coverage measurement"
default GCOV_KERNEL if COMPILE_TEST
@@ -49,9 +58,30 @@ config GCOV_KERNEL
and its modules is exported in gcov's format in /sys/kernel/debug/gcov,
see Documentation/dev-tools/gcov.rst.
+config LLVM_COV_KERNEL
+ bool "Clang's source-based code coverage"
+ depends on CC_IS_CLANG && CLANG_VERSION >= 190000
+ depends on DEBUG_FS
+ depends on ARCH_HAS_LLVM_COV
+ depends on !ARCH_WANTS_NO_INSTR || CC_HAS_NO_PROFILE_FN_ATTR
+ help
+ This option enables Clang's Source-based Code Coverage. It
+ instruments the code in the compiler's front end, so that coverage
+ data maps back to the source code exactly, regardless of the
+ optimizations applied.
+
+ The raw profile is exported in /sys/kernel/debug/coverage/llvm/profraw.
+ Convert it with "llvm-profdata merge" and analyze it with "llvm-cov"
+ against vmlinux, using the LLVM version that built the kernel.
+
+ Only code built into the kernel image is instrumented.
+
endchoice
config COVERAGE_KERNEL
+ def_bool GCOV_KERNEL || LLVM_COV_KERNEL
+
+config COVERAGE_MODULES
def_bool GCOV_KERNEL
config GCOV_PROFILE_ALL
@@ -64,7 +94,8 @@ config GCOV_PROFILE_ALL
config COVERAGE_PROFILE_ALL
bool "Profile entire kernel"
depends on !COMPILE_TEST
- depends on GCOV_KERNEL && ARCH_HAS_GCOV_PROFILE_ALL
+ depends on (GCOV_KERNEL && ARCH_HAS_GCOV_PROFILE_ALL) || \
+ (LLVM_COV_KERNEL && ARCH_HAS_LLVM_COV_PROFILE_ALL)
default GCOV_PROFILE_ALL
help
This option activates coverage instrumentation for the entire kernel.
diff --git a/kernel/coverage/Makefile b/kernel/coverage/Makefile
index 881fada442bd1..d783272d9974d 100644
--- a/kernel/coverage/Makefile
+++ b/kernel/coverage/Makefile
@@ -2,6 +2,7 @@
obj-$(or $(CONFIG_COVERAGE_KERNEL),$(CONFIG_KCOV)) += core.o
obj-$(CONFIG_GCOV_KERNEL) += gcov/
+obj-$(CONFIG_LLVM_COV_KERNEL) += llvm/
obj-$(CONFIG_KCOV) += kcov.o
# Don't self-instrument.
diff --git a/kernel/coverage/llvm/Makefile b/kernel/coverage/llvm/Makefile
new file mode 100644
index 0000000000000..7090171b070eb
--- /dev/null
+++ b/kernel/coverage/llvm/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+COVERAGE_PROFILE := n
+
+obj-y := fs.o
diff --git a/kernel/coverage/llvm/fs.c b/kernel/coverage/llvm/fs.c
new file mode 100644
index 0000000000000..e90c325daeba3
--- /dev/null
+++ b/kernel/coverage/llvm/fs.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Sami Tolvanen <samitolvanen@google.com>, Google, Inc.
+ * Copyright (C) 2024 Jinghao Jia <jinghao7@illinois.edu>, UIUC
+ * Copyright (C) 2024 Wentao Zhang <wentaoz5@illinois.edu>, UIUC
+ */
+
+#define pr_fmt(fmt) "llvm-cov: " fmt
+
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/vmalloc.h>
+
+#include "../coverage.h"
+#include "llvm-cov.h"
+
+/*
+ * Serializes taking a snapshot of the profile data against resetting it. The
+ * instrumented code updates the counters and bitmaps without taking it, so a
+ * snapshot is not atomic with respect to code running on other CPUs.
+ */
+static DEFINE_MUTEX(llvm_cov_mutex);
+
+struct llvm_cov_private_data {
+ char *buffer;
+ unsigned long size;
+};
+
+/*
+ * Raw profile data format:
+ * https://llvm.org/docs/InstrProfileFormat.html#raw-profile-format. Only the
+ * information relevant to Source-based Code Coverage is populated. Binary IDs,
+ * continuous mode, single-byte mode, value profiling, type profiling and
+ * uniform counters are not implemented.
+ */
+
+static void llvm_cov_fill_raw_profile_header(void **buffer)
+{
+ struct __llvm_profile_header *header = *buffer;
+
+ header->magic = INSTR_PROF_RAW_MAGIC_64;
+ header->version = INSTR_PROF_RAW_VERSION;
+ header->binary_ids_size = 0;
+ header->num_data = __llvm_prf_data_count();
+ header->padding_bytes_before_counters = 0;
+ header->num_counters = __llvm_prf_cnts_count();
+ header->padding_bytes_after_counters =
+ __llvm_prf_get_padding(__llvm_prf_cnts_size());
+ header->num_bitmap_bytes = __llvm_prf_bits_size();
+ header->padding_bytes_after_bitmap_bytes =
+ __llvm_prf_get_padding(__llvm_prf_bits_size());
+#if CONFIG_CLANG_VERSION >= 230000
+ header->num_uniform_counters = 0;
+ header->padding_bytes_after_uniform_counters = 0;
+ header->uniform_counters_delta = 0;
+#endif
+ header->names_size = __llvm_prf_names_size();
+ header->counters_delta = (u64)__llvm_prf_cnts_start -
+ (u64)__llvm_prf_data_start;
+ header->bitmap_delta = (u64)__llvm_prf_bits_start -
+ (u64)__llvm_prf_data_start;
+ header->names_delta = (u64)__llvm_prf_names_start;
+ header->num_v_tables = 0;
+ header->v_names_size = 0;
+ header->value_kind_last = IPVK_LAST;
+
+ *buffer += sizeof(*header);
+}
+
+/*
+ * Copy the source into the buffer, incrementing the pointer into buffer in the
+ * process.
+ */
+static void llvm_cov_copy_section_to_buffer(void **buffer, void *src,
+ unsigned long size)
+{
+ memcpy(*buffer, src, size);
+ *buffer += size;
+}
+
+static unsigned long llvm_cov_get_raw_profile_size(void)
+{
+ return sizeof(struct __llvm_profile_header) +
+ __llvm_prf_data_size() +
+ __llvm_prf_cnts_size() +
+ __llvm_prf_get_padding(__llvm_prf_cnts_size()) +
+ __llvm_prf_bits_size() +
+ __llvm_prf_get_padding(__llvm_prf_bits_size()) +
+ __llvm_prf_names_size() +
+ __llvm_prf_get_padding(__llvm_prf_names_size());
+}
+
+/*
+ * Serialize in-memory data into a format LLVM tools can understand
+ * (https://llvm.org/docs/InstrProfileFormat.html#raw-profile-format)
+ */
+static int llvm_cov_serialize_raw_profile(struct llvm_cov_private_data *p)
+{
+ void *buffer;
+
+ p->size = llvm_cov_get_raw_profile_size();
+ p->buffer = vzalloc(p->size);
+ if (!p->buffer)
+ return -ENOMEM;
+
+ buffer = p->buffer;
+
+ llvm_cov_fill_raw_profile_header(&buffer);
+ llvm_cov_copy_section_to_buffer(&buffer, __llvm_prf_data_start,
+ __llvm_prf_data_size());
+ llvm_cov_copy_section_to_buffer(&buffer, __llvm_prf_cnts_start,
+ __llvm_prf_cnts_size());
+ buffer += __llvm_prf_get_padding(__llvm_prf_cnts_size());
+ llvm_cov_copy_section_to_buffer(&buffer, __llvm_prf_bits_start,
+ __llvm_prf_bits_size());
+ buffer += __llvm_prf_get_padding(__llvm_prf_bits_size());
+ llvm_cov_copy_section_to_buffer(&buffer, __llvm_prf_names_start,
+ __llvm_prf_names_size());
+
+ return 0;
+}
+
+/* open() implementation for llvm-cov data file. */
+static int llvm_cov_open(struct inode *inode, struct file *file)
+{
+ struct llvm_cov_private_data *data;
+ int err;
+
+ data = kzalloc_obj(*data);
+ if (!data)
+ return -ENOMEM;
+
+ mutex_lock(&llvm_cov_mutex);
+ err = llvm_cov_serialize_raw_profile(data);
+ mutex_unlock(&llvm_cov_mutex);
+
+ if (err) {
+ kfree(data);
+ return err;
+ }
+
+ file->private_data = data;
+ return 0;
+}
+
+/* read() implementation for llvm-cov data file. */
+static ssize_t llvm_cov_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ struct llvm_cov_private_data *data = file->private_data;
+
+ return simple_read_from_buffer(buf, count, ppos, data->buffer,
+ data->size);
+}
+
+/* release() implementation for llvm-cov data file. */
+static int llvm_cov_release(struct inode *inode, struct file *file)
+{
+ struct llvm_cov_private_data *data = file->private_data;
+
+ vfree(data->buffer);
+ kfree(data);
+ return 0;
+}
+
+static const struct file_operations llvm_cov_data_fops = {
+ .owner = THIS_MODULE,
+ .open = llvm_cov_open,
+ .read = llvm_cov_read,
+ .llseek = default_llseek,
+ .release = llvm_cov_release,
+};
+
+static void llvm_cov_reset(void)
+{
+ mutex_lock(&llvm_cov_mutex);
+ memset(__llvm_prf_cnts_start, 0, __llvm_prf_cnts_size());
+ memset(__llvm_prf_bits_start, 0, __llvm_prf_bits_size());
+ mutex_unlock(&llvm_cov_mutex);
+}
+
+static const struct coverage_backend llvm_cov_backend = {
+ .name = "llvm",
+ .reset = llvm_cov_reset,
+};
+
+static int __init llvm_cov_init(void)
+{
+ struct dentry *dir;
+
+ dir = debugfs_create_dir("llvm", coverage_debugfs_root());
+ debugfs_create_file("profraw", 0400, dir, NULL, &llvm_cov_data_fops);
+ coverage_register_backend(&llvm_cov_backend);
+
+ return 0;
+}
+device_initcall(llvm_cov_init);
diff --git a/kernel/coverage/llvm/llvm-cov.h b/kernel/coverage/llvm/llvm-cov.h
new file mode 100644
index 0000000000000..61c76e803f88b
--- /dev/null
+++ b/kernel/coverage/llvm/llvm-cov.h
@@ -0,0 +1,139 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Sami Tolvanen <samitolvanen@google.com>, Google, Inc.
+ * Copyright (C) 2024 Jinghao Jia <jinghao7@illinois.edu>, UIUC
+ * Copyright (C) 2024 Wentao Zhang <wentaoz5@illinois.edu>, UIUC
+ */
+
+#ifndef _LLVM_COV_H
+#define _LLVM_COV_H
+
+#include <linux/types.h>
+
+/*
+ * These internal LLVM definitions must match the compiler version. See
+ * llvm/include/llvm/ProfileData/InstrProfData.inc in LLVM's source code.
+ */
+
+#define INSTR_PROF_RAW_MAGIC_64 \
+ ((u64)255 << 56 | \
+ (u64)'l' << 48 | \
+ (u64)'p' << 40 | \
+ (u64)'r' << 32 | \
+ (u64)'o' << 24 | \
+ (u64)'f' << 16 | \
+ (u64)'r' << 8 | \
+ (u64)129)
+
+#if CONFIG_CLANG_VERSION >= 230000
+#define INSTR_PROF_RAW_VERSION 11
+#else
+#define INSTR_PROF_RAW_VERSION 10
+#endif
+#define INSTR_PROF_DATA_ALIGNMENT 8
+#define IPVK_LAST 2
+
+/**
+ * struct __llvm_profile_header - represents the raw profile header data
+ * structure. Description of each member can be found here:
+ * https://llvm.org/docs/InstrProfileFormat.html#header.
+ */
+struct __llvm_profile_header {
+ u64 magic;
+ u64 version;
+ u64 binary_ids_size;
+ u64 num_data;
+ u64 padding_bytes_before_counters;
+ u64 num_counters;
+ u64 padding_bytes_after_counters;
+ u64 num_bitmap_bytes;
+ u64 padding_bytes_after_bitmap_bytes;
+#if CONFIG_CLANG_VERSION >= 230000
+ u64 num_uniform_counters;
+ u64 padding_bytes_after_uniform_counters;
+ u64 uniform_counters_delta;
+#endif
+ u64 names_size;
+ u64 counters_delta;
+ u64 bitmap_delta;
+ u64 names_delta;
+ u64 num_v_tables;
+ u64 v_names_size;
+ u64 value_kind_last;
+};
+
+/**
+ * struct __llvm_profile_data - represents the per-function control structure.
+ * Description of each member can be found here:
+ * https://llvm.org/docs/InstrProfileFormat.html#profile-metadata. To measure
+ * Source-based Code Coverage, the internals of this struct don't matter at run
+ * time. The only purpose of the definition below is to run sizeof() against it
+ * so that we can calculate the "num_data" field in header.
+ */
+struct __llvm_profile_data {
+ const u64 name_ref;
+ const u64 func_hash;
+ const void *counter_ptr;
+#if CONFIG_CLANG_VERSION >= 230000
+ const void *uniform_counter_ptr;
+#endif
+ const void *bitmap_ptr;
+ const void *function_pointer;
+ void *values;
+ const u32 num_counters;
+ const u16 num_value_sites[IPVK_LAST + 1];
+#if CONFIG_CLANG_VERSION >= 230000
+ const u16 offload_device_wave_size;
+#endif
+ const u32 num_bitmap_bytes;
+} __aligned(INSTR_PROF_DATA_ALIGNMENT);
+
+/* Payload sections */
+
+extern struct __llvm_profile_data __llvm_prf_data_start[];
+extern struct __llvm_profile_data __llvm_prf_data_end[];
+
+extern u64 __llvm_prf_cnts_start[];
+extern u64 __llvm_prf_cnts_end[];
+
+extern char __llvm_prf_names_start[];
+extern char __llvm_prf_names_end[];
+
+extern char __llvm_prf_bits_start[];
+extern char __llvm_prf_bits_end[];
+
+#define __DEFINE_SECTION_SIZE(s) \
+ static inline unsigned long __llvm_prf_ ## s ## _size(void) \
+ { \
+ unsigned long start = \
+ (unsigned long)__llvm_prf_ ## s ## _start; \
+ unsigned long end = \
+ (unsigned long)__llvm_prf_ ## s ## _end; \
+ return end - start; \
+ }
+#define __DEFINE_SECTION_COUNT(s) \
+ static inline unsigned long __llvm_prf_ ## s ## _count(void) \
+ { \
+ return __llvm_prf_ ## s ## _size() / \
+ sizeof(__llvm_prf_ ## s ## _start[0]); \
+ }
+
+__DEFINE_SECTION_SIZE(data)
+__DEFINE_SECTION_SIZE(cnts)
+__DEFINE_SECTION_SIZE(names)
+__DEFINE_SECTION_SIZE(bits)
+
+__DEFINE_SECTION_COUNT(data)
+__DEFINE_SECTION_COUNT(cnts)
+__DEFINE_SECTION_COUNT(names)
+__DEFINE_SECTION_COUNT(bits)
+
+#undef __DEFINE_SECTION_SIZE
+#undef __DEFINE_SECTION_COUNT
+
+static inline unsigned long __llvm_prf_get_padding(unsigned long size)
+{
+ return 7 & (sizeof(u64) - size % sizeof(u64));
+}
+
+#endif /* _LLVM_COV_H */
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index e0dc21e009522..77d1ab47125be 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -42,10 +42,13 @@ _cpp_flags = $(KBUILD_CPPFLAGS) $(cppflags-y) $(CPPFLAGS_$(target-stem).lds)
# depending on variables COVERAGE_PROFILE_obj.o, COVERAGE_PROFILE and
# CONFIG_COVERAGE_PROFILE_ALL (in this order)
#
+# Modules are only instrumented if the coverage implementation supports them.
+#
ifeq ($(CONFIG_COVERAGE_KERNEL),y)
-_c_flags += $(if $(patsubst n%,, \
+_c_flags += $(if $(if $(part-of-module),$(CONFIG_COVERAGE_MODULES),y), \
+ $(if $(patsubst n%,, \
$(COVERAGE_PROFILE_$(target-stem).o)$(COVERAGE_PROFILE)$(if $(is-kernel-object),$(CONFIG_COVERAGE_PROFILE_ALL))), \
- $(CFLAGS_COVERAGE))
+ $(CFLAGS_COVERAGE)))
endif
#
diff --git a/scripts/Makefile.llvm-cov b/scripts/Makefile.llvm-cov
new file mode 100644
index 0000000000000..5babb09b6d3e6
--- /dev/null
+++ b/scripts/Makefile.llvm-cov
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+
+CFLAGS_COVERAGE := -fprofile-instr-generate -fcoverage-mapping \
+ -fprofile-update=atomic
+
+export CFLAGS_COVERAGE
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH v2 15/20] x86: Enable Clang's source-based code coverage
2026-11-03 1:52 [PATCH v2 00/20] Unify code coverage and add Clang source-based coverage with MC/DC Sasha Levin
` (4 preceding siblings ...)
2026-11-03 1:52 ` [PATCH v2 14/20] coverage: add Clang's source-based " Sasha Levin
@ 2026-11-03 1:52 ` Sasha Levin
2026-11-03 1:52 ` [PATCH v2 16/20] coverage: add MC/DC measurement Sasha Levin
` (3 subsequent siblings)
9 siblings, 0 replies; 21+ messages in thread
From: Sasha Levin @ 2026-11-03 1:52 UTC (permalink / raw)
To: linux-kernel
Cc: Wentao Zhang, Bill Wendling, Borislav Petkov, Chuck Wolber,
Dave Hansen, H. Peter Anvin, Ingo Molnar, Justin Stitt, llvm,
Marco Elver, Masami Hiramatsu, Nathan Chancellor,
Nick Desaulniers, Peter Zijlstra, Thomas Gleixner, x86
Place the sections of Clang's source-based code coverage in the x86
linker script and allow it to be used, including for the entire
kernel, on x86_64.
Originally-by: Wentao Zhang <wentaoz5@illinois.edu>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
arch/x86/Kconfig | 2 ++
arch/x86/kernel/vmlinux.lds.S | 2 ++
2 files changed, 4 insertions(+)
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 58dce0b66179b..988add4916411 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -89,6 +89,8 @@ config X86
select ARCH_HAS_FORTIFY_SOURCE
select ARCH_HAS_GCOV_PROFILE_ALL
select ARCH_HAS_KCOV if X86_64
+ select ARCH_HAS_LLVM_COV if X86_64
+ select ARCH_HAS_LLVM_COV_PROFILE_ALL if X86_64
select ARCH_HAS_KERNEL_FPU_SUPPORT
select ARCH_HAS_MEM_ENCRYPT
select ARCH_HAS_MEMBARRIER_SYNC_CORE
diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S
index 2438b89a4620a..98f41b943fe63 100644
--- a/arch/x86/kernel/vmlinux.lds.S
+++ b/arch/x86/kernel/vmlinux.lds.S
@@ -212,6 +212,8 @@ SECTIONS
BUG_TABLE
+ LLVM_COV_DATA
+
ORC_UNWIND_TABLE
/* Init code and data - will be freed after init */
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH v2 16/20] coverage: add MC/DC measurement
2026-11-03 1:52 [PATCH v2 00/20] Unify code coverage and add Clang source-based coverage with MC/DC Sasha Levin
` (5 preceding siblings ...)
2026-11-03 1:52 ` [PATCH v2 15/20] x86: Enable " Sasha Levin
@ 2026-11-03 1:52 ` Sasha Levin
2026-11-13 0:45 ` Nathan Chancellor
2026-11-03 1:52 ` [PATCH v2 19/20] gcov: add MC/DC condition coverage support Sasha Levin
` (2 subsequent siblings)
9 siblings, 1 reply; 21+ messages in thread
From: Sasha Levin @ 2026-11-03 1:52 UTC (permalink / raw)
To: linux-kernel
Cc: Wentao Zhang, Alexander Potapenko, Andrey Konovalov,
Bill Wendling, Chuck Wolber, Dmitry Vyukov, Justin Stitt,
kasan-dev, linux-kbuild, llvm, Marco Elver, Masami Hiramatsu,
Nathan Chancellor, Nick Desaulniers, Nicolas Schier,
Peter Oberparleiter, Peter Zijlstra, Thomas Gleixner
Add CONFIG_COVERAGE_MCDC to measure Modified Condition/Decision
Coverage (MC/DC) [1], which is required by several automotive and
aviation standards for certifying safety critical software [2]. Start
with Clang's source-based code coverage, which supports it since Clang
18 [3].
In the following example from arch/x86/events/probe.c, measured on
v6.11, llvm-cov gives the MC/DC measurement for the compound logic
decision at line 43:
43| 12| if (msr[bit].test && !msr[bit].test(bit, data))
------------------
|---> MC/DC Decision Region (43:8) to (43:50)
|
| Number of Conditions: 2
| Condition C1 --> (43:8)
| Condition C2 --> (43:25)
|
| Executed MC/DC Test Vectors:
|
| C1, C2 Result
| 1 { T, F = F }
| 2 { T, T = T }
|
| C1-Pair: not covered
| C2-Pair: covered: (1,2)
| MC/DC Coverage for Decision: 50.00%
|
------------------
44| 5| continue;
Only condition C2 was shown to independently affect the outcome of the
decision, so the MC/DC of this decision is 1 out of 2.
MC/DC uses bitmaps to track the executed test vectors, which grow
quickly with the number of conditions. CONFIG_LLVM_COV_MCDC_MAX_CONDITIONS
sets the largest decision to instrument, through -fmcdc-max-conditions.
Larger decisions, and decisions the compiler can't instrument, are left
out of the MC/DC data and only get branch coverage. The compiler warns
about each of them, which is nothing to act on in the code, so disable
those warnings; building with KCFLAGS=-Wpgo-coverage lists them. Clang
only allows disabling them since version 22: older versions keep
warning, and can't be used together with CONFIG_WERROR.
Link: https://en.wikipedia.org/wiki/Modified_condition%2Fdecision_coverage [1]
Link: https://digital-library.theiet.org/content/journals/10.1049/sej.1994.0025 [2]
Link: https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#mc-dc-instrumentation [3]
Originally-by: Wentao Zhang <wentaoz5@illinois.edu>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
kernel/coverage/Kconfig | 26 ++++++++++++++++++++++++++
kernel/coverage/llvm/fs.c | 5 +++--
scripts/Makefile.llvm-cov | 9 +++++++++
3 files changed, 38 insertions(+), 2 deletions(-)
diff --git a/kernel/coverage/Kconfig b/kernel/coverage/Kconfig
index acfcce23e4c97..8d9d5f063aea6 100644
--- a/kernel/coverage/Kconfig
+++ b/kernel/coverage/Kconfig
@@ -106,6 +106,32 @@ config COVERAGE_PROFILE_ALL
larger and run slower. Also be sure to exclude files from profiling
which are not linked to the kernel image to prevent linker errors.
+config COVERAGE_MCDC
+ bool "Measure modified condition/decision coverage (MC/DC)"
+ depends on LLVM_COV_KERNEL
+ depends on CLANG_VERSION >= 220000 || !WERROR
+ help
+ MC/DC verifies that each condition of a boolean decision has been
+ shown to independently affect the outcome of the decision. It is
+ required for certification by safety standards such as DO-178C
+ (avionics) and ISO 26262 (automotive).
+
+ Decisions that the compiler can't instrument are not measured, and
+ only get branch coverage.
+
+ If unsure, say N.
+
+config LLVM_COV_MCDC_MAX_CONDITIONS
+ int "Maximum number of conditions in a decision to instrument"
+ range 6 32767
+ depends on COVERAGE_MCDC && LLVM_COV_KERNEL
+ default 6
+ help
+ This value is passed to the "-fmcdc-max-conditions" option of Clang.
+ Decisions with more conditions are not instrumented. MC/DC uses
+ bitmaps to track the executed test vectors, which grow quickly with
+ the number of conditions.
+
# The per-subsystem COVERAGE_PROFILE_* options used to be GCOV_PROFILE_*.
config GCOV_PROFILE_AMDGPU
bool
diff --git a/kernel/coverage/llvm/fs.c b/kernel/coverage/llvm/fs.c
index e90c325daeba3..16ada19650d07 100644
--- a/kernel/coverage/llvm/fs.c
+++ b/kernel/coverage/llvm/fs.c
@@ -184,8 +184,9 @@ static void llvm_cov_reset(void)
}
static const struct coverage_backend llvm_cov_backend = {
- .name = "llvm",
- .reset = llvm_cov_reset,
+ .name = "llvm",
+ .features = IS_ENABLED(CONFIG_COVERAGE_MCDC) ? "mcdc" : NULL,
+ .reset = llvm_cov_reset,
};
static int __init llvm_cov_init(void)
diff --git a/scripts/Makefile.llvm-cov b/scripts/Makefile.llvm-cov
index 5babb09b6d3e6..e2875836291b8 100644
--- a/scripts/Makefile.llvm-cov
+++ b/scripts/Makefile.llvm-cov
@@ -3,4 +3,13 @@
CFLAGS_COVERAGE := -fprofile-instr-generate -fcoverage-mapping \
-fprofile-update=atomic
+ifdef CONFIG_COVERAGE_MCDC
+CFLAGS_COVERAGE += -fcoverage-mcdc \
+ -Xclang -fmcdc-max-conditions=$(CONFIG_LLVM_COV_MCDC_MAX_CONDITIONS)
+# Decisions that MC/DC leaves out are nothing to fix in the code. Disable the
+# warnings here rather than in CFLAGS_COVERAGE, so that KCFLAGS=-Wpgo-coverage
+# can list them. Clang only allows this since version 22.
+KBUILD_CFLAGS += $(call cc-disable-warning, pgo-coverage)
+endif
+
export CFLAGS_COVERAGE
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH v2 16/20] coverage: add MC/DC measurement
2026-11-03 1:52 ` [PATCH v2 16/20] coverage: add MC/DC measurement Sasha Levin
@ 2026-11-13 0:45 ` Nathan Chancellor
0 siblings, 0 replies; 21+ messages in thread
From: Nathan Chancellor @ 2026-11-13 0:45 UTC (permalink / raw)
To: Sasha Levin
Cc: linux-kernel, Wentao Zhang, Alexander Potapenko, Andrey Konovalov,
Bill Wendling, Chuck Wolber, Dmitry Vyukov, Justin Stitt,
kasan-dev, linux-kbuild, llvm, Marco Elver, Masami Hiramatsu,
Nathan Chancellor, Nick Desaulniers, Nicolas Schier,
Peter Oberparleiter, Peter Zijlstra, Thomas Gleixner
> [ ... 63 lines skipped ... ]
> +config COVERAGE_MCDC
> + bool "Measure modified condition/decision coverage (MC/DC)"
> + depends on LLVM_COV_KERNEL
> + depends on CLANG_VERSION >= 220000 || !WERROR
I don't have a super strong opinion here but I think I would rather see
this just hard depend on Clang >= 22.1.0 rather than let these warnings
pollute the build without -Werror. Maybe something for the future if we
get too many reports.
> [ ... 49 lines skipped ... ]
> +ifdef CONFIG_COVERAGE_MCDC
> +CFLAGS_COVERAGE += -fcoverage-mcdc \
> + -Xclang -fmcdc-max-conditions=$(CONFIG_LLVM_COV_MCDC_MAX_CONDITIONS)
> +# Decisions that MC/DC leaves out are nothing to fix in the code. Disable the
> +# warnings here rather than in CFLAGS_COVERAGE, so that KCFLAGS=-Wpgo-coverage
> +# can list them. Clang only allows this since version 22.
> +KBUILD_CFLAGS += $(call cc-disable-warning, pgo-coverage)
Please make this:
KBUILD_CFLAGS += $(call cc-option,-Wno-pgo-coverage)
This might even want to be in scripts/Kconfig.toolchain as
config CC_OPT_WNO_PGO_COVERAGE
# clang-22: https://github.com/llvm/llvm-project/commit/80887c7de9d82c809555f52948af1a5d7484e28
# gcc: not supported
def_string "$(cc-option-str,-Wno-pgo-coverage)"
so that it will be easy to clean this up when we use clang-22 as a
minimum version.
--
Cheers,
Nathan
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v2 19/20] gcov: add MC/DC condition coverage support
2026-11-03 1:52 [PATCH v2 00/20] Unify code coverage and add Clang source-based coverage with MC/DC Sasha Levin
` (6 preceding siblings ...)
2026-11-03 1:52 ` [PATCH v2 16/20] coverage: add MC/DC measurement Sasha Levin
@ 2026-11-03 1:52 ` Sasha Levin
2026-11-05 17:56 ` Peter Oberparleiter
2026-11-03 1:52 ` [PATCH v2 20/20] Documentation: dev-tools: document the common code coverage interface Sasha Levin
2026-11-05 17:36 ` [PATCH v2 00/20] Unify code coverage and add Clang source-based coverage with MC/DC Peter Oberparleiter
9 siblings, 1 reply; 21+ messages in thread
From: Sasha Levin @ 2026-11-03 1:52 UTC (permalink / raw)
To: linux-kernel
Cc: Alexander Potapenko, Andrey Konovalov, Anton Ivanov,
Bill Wendling, Borislav Petkov, Chuck Wolber, Dave Hansen,
Dmitry Vyukov, H. Peter Anvin, Ingo Molnar, Johannes Berg,
Justin Stitt, kasan-dev, linux-kbuild, linux-um, llvm,
Marco Elver, Masami Hiramatsu, Nathan Chancellor,
Nick Desaulniers, Nicolas Schier, Peter Oberparleiter,
Peter Zijlstra, Richard Weinberger, Thomas Gleixner, x86
Support CONFIG_COVERAGE_MCDC with gcov, using GCC's MC/DC condition
coverage instrumentation (-fcondition-coverage), which is available
since GCC 14. The data is shown by "gcov --conditions".
Add -fcondition-coverage to the gcov compiler flags when enabled, and
disable the warnings about decisions it leaves out, as is done for
Clang. Both are gated on the config option to avoid Clang warnings
about unknown options.
Also add -fcondition-coverage to CFLAGS_REMOVE in the x86 UML vDSO
Makefile to prevent instrumentation of userspace vDSO code.
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
arch/x86/um/vdso/Makefile | 4 ++--
kernel/coverage/Kconfig | 7 +++++--
kernel/coverage/gcov/fs.c | 5 +++--
scripts/Makefile.gcov | 5 +++++
4 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/arch/x86/um/vdso/Makefile b/arch/x86/um/vdso/Makefile
index 7664cbedbe30f..adfc57c1a726d 100644
--- a/arch/x86/um/vdso/Makefile
+++ b/arch/x86/um/vdso/Makefile
@@ -42,8 +42,8 @@ $(vobjs): KBUILD_CFLAGS += $(CFL)
#
# vDSO code runs in userspace and -pg doesn't help with profiling anyway.
#
-CFLAGS_REMOVE_vdso-note.o = -pg -fprofile-arcs -ftest-coverage
-CFLAGS_REMOVE_um_vdso.o = -pg -fprofile-arcs -ftest-coverage
+CFLAGS_REMOVE_vdso-note.o = -pg -fprofile-arcs -ftest-coverage -fcondition-coverage
+CFLAGS_REMOVE_um_vdso.o = -pg -fprofile-arcs -ftest-coverage -fcondition-coverage
#
# The DSO images are built using a special linker script.
diff --git a/kernel/coverage/Kconfig b/kernel/coverage/Kconfig
index 8d9d5f063aea6..3429bc902c934 100644
--- a/kernel/coverage/Kconfig
+++ b/kernel/coverage/Kconfig
@@ -108,8 +108,8 @@ config COVERAGE_PROFILE_ALL
config COVERAGE_MCDC
bool "Measure modified condition/decision coverage (MC/DC)"
- depends on LLVM_COV_KERNEL
- depends on CLANG_VERSION >= 220000 || !WERROR
+ depends on LLVM_COV_KERNEL || (GCOV_KERNEL && CC_HAS_CONDITION_COVERAGE)
+ depends on !LLVM_COV_KERNEL || CLANG_VERSION >= 220000 || !WERROR
help
MC/DC verifies that each condition of a boolean decision has been
shown to independently affect the outcome of the decision. It is
@@ -119,6 +119,9 @@ config COVERAGE_MCDC
Decisions that the compiler can't instrument are not measured, and
only get branch coverage.
+ With gcov this requires GCC 14 or later, and the data is shown by
+ "gcov --conditions".
+
If unsure, say N.
config LLVM_COV_MCDC_MAX_CONDITIONS
diff --git a/kernel/coverage/gcov/fs.c b/kernel/coverage/gcov/fs.c
index a761cfb837414..689b8b9a0d16a 100644
--- a/kernel/coverage/gcov/fs.c
+++ b/kernel/coverage/gcov/fs.c
@@ -866,8 +866,9 @@ void gcov_event(enum gcov_action action, struct gcov_info *info)
}
static const struct coverage_backend gcov_backend = {
- .name = "gcov",
- .reset = gcov_reset_all,
+ .name = "gcov",
+ .features = IS_ENABLED(CONFIG_COVERAGE_MCDC) ? "mcdc" : NULL,
+ .reset = gcov_reset_all,
};
/* Create debugfs entries. */
diff --git a/scripts/Makefile.gcov b/scripts/Makefile.gcov
index 1cfe04d89b0a2..977c8ee023f68 100644
--- a/scripts/Makefile.gcov
+++ b/scripts/Makefile.gcov
@@ -18,4 +18,9 @@ CFLAGS_COVERAGE += $(call try-run,\
cmp -s "$$TMP.ubase" "$$TMP.utest",\
-fprofile-update=prefer-atomic)
endif
+ifdef CONFIG_COVERAGE_MCDC
+CFLAGS_COVERAGE += -fcondition-coverage
+# As for Clang: KCFLAGS=-Wcoverage-too-many-conditions lists what's left out.
+KBUILD_CFLAGS += -Wno-coverage-too-many-conditions
+endif
export CFLAGS_COVERAGE
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH v2 19/20] gcov: add MC/DC condition coverage support
2026-11-03 1:52 ` [PATCH v2 19/20] gcov: add MC/DC condition coverage support Sasha Levin
@ 2026-11-05 17:56 ` Peter Oberparleiter
2026-11-06 1:05 ` Sasha Levin
0 siblings, 1 reply; 21+ messages in thread
From: Peter Oberparleiter @ 2026-11-05 17:56 UTC (permalink / raw)
To: Sasha Levin, linux-kernel
Cc: Alexander Potapenko, Andrey Konovalov, Anton Ivanov,
Bill Wendling, Borislav Petkov, Chuck Wolber, Dave Hansen,
Dmitry Vyukov, H. Peter Anvin, Ingo Molnar, Johannes Berg,
Justin Stitt, kasan-dev, linux-kbuild, linux-um, llvm,
Marco Elver, Masami Hiramatsu, Nathan Chancellor,
Nick Desaulniers, Nicolas Schier, Peter Zijlstra,
Richard Weinberger, Thomas Gleixner, x86
On 03.11.2026 02:52, Sasha Levin wrote:
> Support CONFIG_COVERAGE_MCDC with gcov, using GCC's MC/DC condition
> coverage instrumentation (-fcondition-coverage), which is available
> since GCC 14. The data is shown by "gcov --conditions".
>
> Add -fcondition-coverage to the gcov compiler flags when enabled, and
> disable the warnings about decisions it leaves out, as is done for
> Clang. Both are gated on the config option to avoid Clang warnings
> about unknown options.
>
> Also add -fcondition-coverage to CFLAGS_REMOVE in the x86 UML vDSO
> Makefile to prevent instrumentation of userspace vDSO code.
>
> Signed-off-by: Sasha Levin <sashal@kernel.org>
Looks good, minor comment below.
Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com>
[...]
> @@ -108,8 +108,8 @@ config COVERAGE_PROFILE_ALL
>
> config COVERAGE_MCDC
> bool "Measure modified condition/decision coverage (MC/DC)"
> - depends on LLVM_COV_KERNEL
> - depends on CLANG_VERSION >= 220000 || !WERROR
> + depends on LLVM_COV_KERNEL || (GCOV_KERNEL && CC_HAS_CONDITION_COVERAGE)
> + depends on !LLVM_COV_KERNEL || CLANG_VERSION >= 220000 || !WERROR
Would it make sense to transform this for readability, i.e.
depends on !(LLVM_COV_KERNEL && CLANG_VERSION < 220000 && WERROR)
--
Peter Oberparleiter
Linux on IBM Z Development - IBM Germany R&D
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 19/20] gcov: add MC/DC condition coverage support
2026-11-05 17:56 ` Peter Oberparleiter
@ 2026-11-06 1:05 ` Sasha Levin
0 siblings, 0 replies; 21+ messages in thread
From: Sasha Levin @ 2026-11-06 1:05 UTC (permalink / raw)
To: Peter Oberparleiter
Cc: linux-kernel, Alexander Potapenko, Andrey Konovalov, Anton Ivanov,
Bill Wendling, Borislav Petkov, Chuck Wolber, Dave Hansen,
Dmitry Vyukov, H. Peter Anvin, Ingo Molnar, Johannes Berg,
Justin Stitt, kasan-dev, linux-kbuild, linux-um, llvm,
Marco Elver, Masami Hiramatsu, Nathan Chancellor,
Nick Desaulniers, Nicolas Schier, Peter Zijlstra,
Richard Weinberger, Thomas Gleixner, x86
On Thu, Nov 05, 2026 at 06:56:10PM +0100, Peter Oberparleiter wrote:
>> + depends on LLVM_COV_KERNEL || (GCOV_KERNEL && CC_HAS_CONDITION_COVERAGE)
>> + depends on !LLVM_COV_KERNEL || CLANG_VERSION >= 220000 || !WERROR
>
>Would it make sense to transform this for readability, i.e.
>
>depends on !(LLVM_COV_KERNEL && CLANG_VERSION < 220000 && WERROR)
Yes, that reads better. I'll switch to it in v3, thanks.
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v2 20/20] Documentation: dev-tools: document the common code coverage interface
2026-11-03 1:52 [PATCH v2 00/20] Unify code coverage and add Clang source-based coverage with MC/DC Sasha Levin
` (7 preceding siblings ...)
2026-11-03 1:52 ` [PATCH v2 19/20] gcov: add MC/DC condition coverage support Sasha Levin
@ 2026-11-03 1:52 ` Sasha Levin
2026-11-05 17:36 ` [PATCH v2 00/20] Unify code coverage and add Clang source-based coverage with MC/DC Peter Oberparleiter
9 siblings, 0 replies; 21+ messages in thread
From: Sasha Levin @ 2026-11-03 1:52 UTC (permalink / raw)
To: linux-kernel
Cc: Alexander Potapenko, Andrey Konovalov, Bill Wendling,
Chuck Wolber, Dmitry Vyukov, Jonathan Corbet, Justin Stitt,
kasan-dev, linux-doc, llvm, Marco Elver, Masami Hiramatsu,
Nathan Chancellor, Nick Desaulniers, Peter Oberparleiter,
Peter Zijlstra, Randy Dunlap, Shuah Khan, Thomas Gleixner,
workflows
Describe the code coverage implementations and what they share: the
Kconfig options, the COVERAGE_PROFILE annotations, the
/sys/kernel/debug/coverage/ interface, MC/DC measurement, and how they
deal with noinstr code. Point to it from the gcov and KCOV
documentation, and document gcov's MC/DC support.
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
Documentation/dev-tools/coverage.rst | 161 +++++++++++++++++++++++++++
Documentation/dev-tools/gcov.rst | 28 +++++
Documentation/dev-tools/index.rst | 1 +
Documentation/dev-tools/kcov.rst | 3 +
MAINTAINERS | 1 +
5 files changed, 194 insertions(+)
create mode 100644 Documentation/dev-tools/coverage.rst
diff --git a/Documentation/dev-tools/coverage.rst b/Documentation/dev-tools/coverage.rst
new file mode 100644
index 0000000000000..f45c6c160379f
--- /dev/null
+++ b/Documentation/dev-tools/coverage.rst
@@ -0,0 +1,161 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=============
+Code coverage
+=============
+
+The kernel can be instrumented to find out which parts of it get executed.
+This serves two different purposes:
+
+* Measuring code coverage, e.g. to assess and improve a test suite, or to
+ show the structural coverage required by safety standards. This is done by
+ one of the code coverage measurement implementations described below.
+
+* Guiding fuzzers. This is done by KCOV, which collects coverage per task;
+ see Documentation/dev-tools/kcov.rst.
+
+All of them are configured in the "Kernel hacking -> Kernel Testing and
+Coverage -> Code coverage" menu, live in ``kernel/coverage/``, and share the
+``/sys/kernel/debug/coverage/`` debugfs directory.
+
+
+Code coverage measurement
+=========================
+
+The implementation is selected by the "Code coverage measurement" choice:
+
+============================ ============== ================================
+Option Compilers Data
+============================ ============== ================================
+``CONFIG_GCOV_KERNEL`` GCC, Clang gcov ``.gcda`` files, see
+ Documentation/dev-tools/gcov.rst
+``CONFIG_LLVM_COV_KERNEL`` Clang 19 and LLVM raw profile of the kernel
+ later image (``.profraw``)
+============================ ============== ================================
+
+The instrumentation and the format of the data are defined by the toolchain,
+so only one implementation can be used at a time. Both only instrument C
+code.
+
+Clang's source-based code coverage (``CONFIG_LLVM_COV_KERNEL``) instruments
+the code in the compiler front end. Its data therefore maps back to the source
+code exactly, regardless of the optimizations applied, and it can show the
+coverage of every condition of a decision. It only instruments code built into
+the kernel image. gcov also supports modules.
+
+
+Selecting the code to instrument
+--------------------------------
+
+``CONFIG_COVERAGE_PROFILE_ALL`` instruments the entire kernel. On architectures
+that don't support it, or to instrument only parts of the kernel, add a line
+like the following to the respective Makefile.
+
+For a single file (e.g. main.o)::
+
+ COVERAGE_PROFILE_main.o := y
+
+For all files in one directory::
+
+ COVERAGE_PROFILE := y
+
+To exclude files from being instrumented even when
+``CONFIG_COVERAGE_PROFILE_ALL`` is enabled, use::
+
+ COVERAGE_PROFILE_main.o := n
+
+and::
+
+ COVERAGE_PROFILE := n
+
+The per-file setting takes precedence over the per-directory one, which takes
+precedence over ``CONFIG_COVERAGE_PROFILE_ALL``.
+
+Only exclude code that can't work when instrumented, for example because it
+runs before the kernel is relocated or is copied elsewhere. Code that must not
+be instrumented for correctness reasons, such as the entry code, is annotated
+``noinstr`` instead; see `noinstr`_.
+
+
+Interface
+---------
+
+``/sys/kernel/debug/coverage/`` contains:
+
+``backend``
+ The name of the selected implementation, ``gcov`` or ``llvm``, followed by
+ ``mcdc`` when MC/DC is measured.
+
+``reset``
+ Writing to this file resets all coverage data of the selected
+ implementation. It does not affect KCOV.
+
+``gcov``
+ A symlink to ``/sys/kernel/debug/gcov``, where gcov keeps its data (see
+ Documentation/dev-tools/gcov.rst).
+
+``llvm/profraw``
+ The raw profile of the kernel image, for Clang's source-based code
+ coverage.
+
+``kcov``
+ A symlink to ``/sys/kernel/debug/kcov`` (see
+ Documentation/dev-tools/kcov.rst).
+
+The instrumented code updates the coverage data without synchronizing with
+readers, so reading the data while other CPUs execute instrumented code
+doesn't give an atomic snapshot, and the same applies to a reset.
+
+With Clang's source-based code coverage, convert the raw profile and create a
+report with the ``llvm-profdata`` and ``llvm-cov`` tools of the LLVM version
+that built the kernel, since the raw profile format changes between releases::
+
+ # cp /sys/kernel/debug/coverage/llvm/profraw vmlinux.profraw
+ $ llvm-profdata merge vmlinux.profraw -o vmlinux.profdata
+ $ llvm-cov report -instr-profile=vmlinux.profdata vmlinux
+ $ llvm-cov show -instr-profile=vmlinux.profdata vmlinux kernel/fork.c
+
+
+MC/DC
+-----
+
+Modified condition/decision coverage (MC/DC) verifies that each condition of a
+boolean decision has been shown to independently affect the outcome of the
+decision. It is required by safety standards such as DO-178C (avionics) and
+ISO 26262 (automotive). Enable it with ``CONFIG_COVERAGE_MCDC``:
+
+* With Clang's source-based code coverage, decisions with more than
+ ``CONFIG_LLVM_COV_MCDC_MAX_CONDITIONS`` conditions are not measured. Show
+ the results with ``llvm-cov show -show-mcdc`` and
+ ``llvm-cov report -show-mcdc-summary``.
+
+* With gcov, GCC 14 or later is required, and decisions with more than 64
+ conditions are not measured. Show the results with ``gcov --conditions``.
+
+Decisions that aren't measured still get branch coverage. To list them, build
+with ``KCFLAGS=-Wpgo-coverage`` (Clang) or
+``KCFLAGS=-Wcoverage-too-many-conditions`` (GCC). Clang only warns about them
+by default before version 22, as it can't disable these warnings.
+
+
+noinstr
+=======
+
+Code annotated ``noinstr`` must not be instrumented; this includes the code
+running on kernel entry and exit, and in NMI context. Outside of
+``instrumentation_begin()``/``instrumentation_end()`` sections, ``noinstr``
+functions may only call other ``noinstr`` functions and ``__always_inline``
+functions.
+
+The code coverage measurement implementations insert their instrumentation
+before functions get inlined, which would carry the instrumentation of an
+``__always_inline`` function into the ``noinstr`` code it is inlined into. To
+avoid that, ``__always_inline`` functions are not instrumented when one of them
+is enabled, and as a consequence they don't show up in the coverage data.
+
+With ``CONFIG_NOINSTR_VALIDATION``, objtool reports coverage counters that get
+updated by ``noinstr`` code outside of such sections.
+
+KCOV instruments the code after inlining and honours ``noinstr`` through the
+``no_sanitize_coverage`` attribute with Clang and GCC 12 or later; with older
+GCC, objtool removes its calls from ``noinstr`` code.
diff --git a/Documentation/dev-tools/gcov.rst b/Documentation/dev-tools/gcov.rst
index b6910c3fc00dd..d2950070f3ca4 100644
--- a/Documentation/dev-tools/gcov.rst
+++ b/Documentation/dev-tools/gcov.rst
@@ -22,6 +22,9 @@ Possible uses:
* minimizing kernel configurations (do I need this option if the
associated code is never run?)
+See Documentation/dev-tools/coverage.rst for the options and interfaces shared
+with the other code coverage implementations.
+
.. _gcov: https://gcc.gnu.org/onlinedocs/gcc/Gcov.html
.. _lcov: https://github.com/linux-test-project/lcov
@@ -180,6 +183,31 @@ b) gcov is run on the BUILD machine
[user@build] gcov -o /tmp/coverage/tmp/out/init main.c
+MC/DC Coverage
+--------------
+
+When using GCC 14 or later with ``CONFIG_COVERAGE_MCDC=y``, Modified
+Condition/Decision Coverage (MC/DC) data is collected alongside standard
+branch coverage. MC/DC verifies that each condition in a decision
+independently affects the decision's outcome.
+
+To view MC/DC data, use::
+
+ gcov --conditions -o /sys/kernel/debug/gcov/path/to/dir file.c
+
+MC/DC coverage is required by safety standards such as DO-178C
+(avionics) and ISO 26262 (automotive).
+
+GCC's condition coverage implementation has a limit of 64 conditions per
+boolean expression, due to the use of 64-bit bitmasks internally.
+Expressions exceeding this limit cannot be instrumented and will produce
+a compiler warning. This does not affect the correctness of the kernel;
+those expressions simply will not have MC/DC coverage data.
+
+Note that MC/DC instrumentation increases binary size and execution
+overhead compared to standard gcov profiling.
+
+
Note on compilers
-----------------
diff --git a/Documentation/dev-tools/index.rst b/Documentation/dev-tools/index.rst
index 59cbb77b33ff4..d670c1398e495 100644
--- a/Documentation/dev-tools/index.rst
+++ b/Documentation/dev-tools/index.rst
@@ -23,6 +23,7 @@ Documentation/process/debugging/index.rst
coccinelle
context-analysis
sparse
+ coverage
kcov
gcov
kasan
diff --git a/Documentation/dev-tools/kcov.rst b/Documentation/dev-tools/kcov.rst
index 1a739290c8ecc..98a30a1a3f88a 100644
--- a/Documentation/dev-tools/kcov.rst
+++ b/Documentation/dev-tools/kcov.rst
@@ -12,6 +12,9 @@ To achieve this goal, it does not collect coverage in soft/hard interrupts
(unless remove coverage collection is enabled, see below) and from some
inherently non-deterministic parts of the kernel (e.g. scheduler, locking).
+KCOV is configured next to the other code coverage implementations, see
+Documentation/dev-tools/coverage.rst.
+
Besides collecting code coverage, KCOV can also collect comparison operands.
See the "Comparison operands collection" section for details.
diff --git a/MAINTAINERS b/MAINTAINERS
index d4523a5fef33e..48ba7abd8951c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6635,6 +6635,7 @@ F: drivers/media/platform/chips-media/coda
CODE COVERAGE
M: Sasha Levin <sashal@kernel.org>
S: Maintained
+F: Documentation/dev-tools/coverage.rst
F: kernel/coverage/Kconfig
F: kernel/coverage/Makefile
F: kernel/coverage/core.c
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH v2 00/20] Unify code coverage and add Clang source-based coverage with MC/DC
2026-11-03 1:52 [PATCH v2 00/20] Unify code coverage and add Clang source-based coverage with MC/DC Sasha Levin
` (8 preceding siblings ...)
2026-11-03 1:52 ` [PATCH v2 20/20] Documentation: dev-tools: document the common code coverage interface Sasha Levin
@ 2026-11-05 17:36 ` Peter Oberparleiter
9 siblings, 0 replies; 21+ messages in thread
From: Peter Oberparleiter @ 2026-11-05 17:36 UTC (permalink / raw)
To: Sasha Levin, linux-kernel
Cc: Adrian Hunter, Alexander Potapenko, Alexander Shishkin,
Andrew Morton, Andrey Konovalov, Andy Lutomirski, Anton Ivanov,
Arnaldo Carvalho de Melo, Arnd Bergmann, Ben Segall,
Bill Wendling, Boqun Feng, Borislav Petkov, Chuck Wolber,
Dave Hansen, Dietmar Eggemann, Dmitry Vyukov, Frantisek Hrbata,
Frederic Weisbecker, H. Peter Anvin, Ian Rogers, Ingo Molnar,
James Clark, Jiri Olsa, Johannes Berg, Jonathan Corbet,
Josh Poimboeuf, Julian Braha, Juri Lelli, Justin Stitt, kasan-dev,
K Prateek Nayak, linux-doc, linux-kbuild, llvm, Marco Elver,
Mark Rutland, Masami Hiramatsu, Mathieu Desnoyers,
Mauro Carvalho Chehab, Mel Gorman, Miguel Ojeda, Namhyung Kim,
Nathan Chancellor, Nick Desaulniers, Nicolas Schier,
Paolo Bonzini, Paul E. McKenney, Peter Zijlstra, Randy Dunlap,
Richard Weinberger, Sean Christopherson, Shuah Khan,
Steven Rostedt, Thomas Gleixner, Valentin Schneider,
Vincent Guittot, Waiman Long, Will Deacon, x86,
Konstantin Khorenko
On 03.11.2026 02:52, Sasha Levin wrote:
> The previous postings of Clang's source-based code coverage [1][2] drew
> objections [3][4]: rather than a third coverage implementation with its
> own interface next to gcov and KCOV, reviewers asked for a unified
> coverage interface, and for coverage instrumentation to honour noinstr.
> This series first restructures the existing coverage support into one
> framework, then adds source-based coverage and MC/DC as part of it.
Thanks for driving these improvements to the kernel coverage
infrastructure! The overall approach taken to unify the different
backends looks sane to me, and I'm looking forward to seeing this
integrated!
I successfully tested the gcov-backend on s390 with GCC 16.1.0, GCC
15.2.0 and latest Clang 24 (git).
[...]
> - Runtime interface: /sys/kernel/debug/coverage/, with "backend" naming
> the backend and its features, "reset" resetting its data, and the
> backend's data files. The existing gcov and kcov entries stay where
> they are, for lcov and syzkaller, and are linked from there.
Just as a FYI - I found a minor incompatibility when using lcov with the
series applied where it fails to automatically determine the kernel base
directory due to the changed location of gcov-kernel base code, but
that's nothing that should affect the kernel series. The issue can be
worked around by specifying the path manually and I'm planning to
address this with a corresponding lcov change once the series is integrated.
--
Peter Oberparleiter
Linux on IBM Z Development - IBM Germany R&D
^ permalink raw reply [flat|nested] 21+ messages in thread