[PATCH RFC v3 04/12] kcov: summarize entry/exit while disabled

From: Jann Horn

Date: Tue Sep 08 2026 - 12:59:03 EST


In case kcov is re-enabled with a different call stack than the one it
was disabled with, emit events that summarize changes to the call stack
so that userspace can continue tracking the call stack across context
switches.

Signed-off-by: Jann Horn <jannh@xxxxxxxxxx>
---
include/linux/kcov.h | 11 ++---------
include/uapi/linux/kcov.h | 2 ++
kernel/kcov.c | 48 +++++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 50 insertions(+), 11 deletions(-)

diff --git a/include/linux/kcov.h b/include/linux/kcov.h
index 6c9f0373022f..357f4de8790a 100644
--- a/include/linux/kcov.h
+++ b/include/linux/kcov.h
@@ -35,15 +35,8 @@ enum kcov_mode {
void kcov_task_init(struct task_struct *t);
void kcov_task_exit(struct task_struct *t);

-#define kcov_prepare_switch(t) \
-do { \
- (t)->kcov_mode |= KCOV_IN_CTXSW; \
-} while (0)
-
-#define kcov_finish_switch(t) \
-do { \
- (t)->kcov_mode &= ~KCOV_IN_CTXSW; \
-} while (0)
+void kcov_prepare_switch(struct task_struct *cur);
+void kcov_finish_switch(struct task_struct *cur);

/* See Documentation/dev-tools/kcov.rst for usage details. */
void kcov_remote_start(u64 handle);
diff --git a/include/uapi/linux/kcov.h b/include/uapi/linux/kcov.h
index 8d8a233bd61f..75c582784055 100644
--- a/include/uapi/linux/kcov.h
+++ b/include/uapi/linux/kcov.h
@@ -48,6 +48,8 @@ enum {
#define KCOV_RECORDFLAG_TYPE_NORMAL 0xf000000000000000
#define KCOV_RECORDFLAG_TYPE_ENTRY 0x0000000000000000
#define KCOV_RECORDFLAG_TYPE_EXIT 0x1000000000000000
+/* Summarized entry/exit events that occurred in an untraced region. */
+#define KCOV_RECORDFLAG_TYPE_EESUM 0x2000000000000000

/*
* The format for the types of collected comparisons.
diff --git a/kernel/kcov.c b/kernel/kcov.c
index 701ad69493bf..712f0f744ec5 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -76,6 +76,8 @@ struct kcov {
* kcov_remote_stop(), see the comment there.
*/
int sequence;
+ int suppressed_stack_delta;
+ int suppressed_stack_mindelta;
};

struct kcov_remote_area {
@@ -256,8 +258,12 @@ void notrace __sanitizer_cov_trace_pc_entry(void)
* This hook replaces __sanitizer_cov_trace_pc() for the function entry
* basic block; it should still emit a record even in classic kcov mode.
*/
- if ((kcov_mode & ~KCOV_EXT_FORMAT) != KCOV_MODE_TRACE_PC)
+ if ((kcov_mode & ~(KCOV_EXT_FORMAT|KCOV_IN_CTXSW)) != KCOV_MODE_TRACE_PC)
return;
+ if (kcov_mode & KCOV_IN_CTXSW) {
+ cur->kcov->suppressed_stack_delta++;
+ return;
+ }
if ((kcov_mode & KCOV_EXT_FORMAT) != 0)
record = (record & KCOV_RECORD_IP_MASK) | KCOV_RECORDFLAG_TYPE_ENTRY;
kcov_add_pc_record(cur, record);
@@ -266,6 +272,7 @@ void notrace __sanitizer_cov_trace_pc_exit(void)
{
struct task_struct *cur = current;
unsigned long record;
+ unsigned int kcov_mode = READ_ONCE(cur->kcov_mode);

/*
* This hook is not called at the beginning of a basic block; the basic
@@ -274,8 +281,16 @@ void notrace __sanitizer_cov_trace_pc_exit(void)
* So unlike __sanitizer_cov_trace_pc_entry(), this PC should only be
* reported in extended mode, where function exit events are recorded.
*/
- if (READ_ONCE(cur->kcov_mode) != KCOV_MODE_TRACE_PC_EXT)
+ if ((kcov_mode & ~KCOV_IN_CTXSW) != KCOV_MODE_TRACE_PC_EXT)
return;
+ if (kcov_mode & KCOV_IN_CTXSW) {
+ struct kcov *kcov = cur->kcov;
+
+ if (kcov->suppressed_stack_mindelta == kcov->suppressed_stack_delta)
+ kcov->suppressed_stack_mindelta--;
+ kcov->suppressed_stack_delta--;
+ return;
+ }
record = (canonicalize_ip(_RET_IP_) & KCOV_RECORD_IP_MASK) | KCOV_RECORDFLAG_TYPE_EXIT;
kcov_add_pc_record(cur, record);
}
@@ -399,6 +414,35 @@ void notrace __sanitizer_cov_trace_switch(kcov_u64 val, void *arg)
EXPORT_SYMBOL(__sanitizer_cov_trace_switch);
#endif /* ifdef CONFIG_KCOV_ENABLE_COMPARISONS */

+void kcov_prepare_switch(struct task_struct *cur)
+{
+#ifdef CONFIG_KCOV_EXT_RECORDS
+ struct kcov *kcov = cur->kcov;
+
+ if (kcov) {
+ kcov->suppressed_stack_mindelta = 0;
+ kcov->suppressed_stack_delta = 0;
+ }
+#endif
+ cur->kcov_mode |= KCOV_IN_CTXSW;
+}
+
+void kcov_finish_switch(struct task_struct *cur)
+{
+ struct kcov *kcov = cur->kcov;
+ unsigned long record;
+
+ cur->kcov_mode &= ~KCOV_IN_CTXSW;
+ if (!IS_ENABLED(CONFIG_KCOV_EXT_RECORDS))
+ return;
+ if ((cur->kcov_mode & KCOV_EXT_FORMAT) == 0)
+ return;
+ record = KCOV_RECORDFLAG_TYPE_EESUM |
+ (((u16)(s16)kcov->suppressed_stack_mindelta)<<16) |
+ (((u16)(s16)kcov->suppressed_stack_delta)<<16);
+ kcov_add_pc_record(cur, record);
+}
+
static void kcov_start(struct task_struct *t, struct kcov *kcov,
unsigned int size, void *area, unsigned int mode,
int sequence)

--
2.55.0.979.g7e5102b832-goog