collection sections. The way a handle is used depends on the context where the
matching code section executes.
+A thread can use two separate KCOV instances to collect remote coverage and
+normal coverage at the same time.
+
KCOV supports collecting remote coverage from the following contexts:
1. Global kernel background tasks. These are the tasks that are spawned during
needs to be passed to the newly spawned local tasks via custom kernel code
modifications. Those tasks should in turn use the passed handle in their
``kcov_remote_start`` and ``kcov_remote_stop`` annotations.
+In the kernel, common handles are wrapped in a ``kcov_common_handle_id``, which
+consumes no space in builds without ``CONFIG_KCOV``; subsystems that integrate
+with this mechanism should not need to use any ``#ifdef CONFIG_KCOV`` or such.
KCOV follows a predefined format for both global and common handles. Each
handle is a ``u64`` integer. Currently, only the one top and the lower 4 bytes
WRITE_ONCE(t->kcov_mode, mode);
}
+/* operates on coverage-generator-owned fields */
static void kcov_stop(struct task_struct *t)
{
WRITE_ONCE(t->kcov_mode, KCOV_MODE_DISABLED);
t->kcov_area = NULL;
}
+/* operates on coverage-generator-owned fields */
static void kcov_task_reset(struct task_struct *t)
{
kcov_stop(t);
t->kcov_sequence = 0;
- t->kcov_handle = 0;
}
void kcov_task_init(struct task_struct *t)
{
kcov_task_reset(t);
+ t->kcov_remote = NULL;
t->kcov_handle = current->kcov_handle;
}
static void kcov_disable(struct task_struct *t, struct kcov *kcov)
__must_hold(&kcov->lock)
{
- kcov_task_reset(t);
- if (kcov->remote)
+ if (kcov->remote) {
+ t->kcov_handle = 0;
+ t->kcov_remote = NULL;
kcov_remote_reset(kcov);
- else
+ } else {
+ kcov_task_reset(t);
kcov_reset(kcov);
+ }
}
static void kcov_get(struct kcov *kcov)
unsigned long flags;
kcov = t->kcov;
- if (kcov == NULL)
- return;
-
- spin_lock_irqsave(&kcov->lock, flags);
- kcov_debug("t = %px, kcov->t = %px\n", t, kcov->t);
- /*
- * For KCOV_ENABLE devices we want to make sure that t->kcov->t == t,
- * which comes down to:
- * WARN_ON(!kcov->remote && kcov->t != t);
- *
- * For KCOV_REMOTE_ENABLE devices, the exiting task is either:
- *
- * 1. A remote task between kcov_remote_start() and kcov_remote_stop().
- * In this case we should print a warning right away, since a task
- * shouldn't be exiting when it's in a kcov coverage collection
- * section. Here t points to the task that is collecting remote
- * coverage, and t->kcov->t points to the thread that created the
- * kcov device. Which means that to detect this case we need to
- * check that t != t->kcov->t, and this gives us the following:
- * WARN_ON(kcov->remote && kcov->t != t);
- *
- * 2. The task that created kcov exiting without calling KCOV_DISABLE,
- * and then again we make sure that t->kcov->t == t:
- * WARN_ON(kcov->remote && kcov->t != t);
- *
- * By combining all three checks into one we get:
- */
- if (WARN_ON(kcov->t != t)) {
+ if (kcov) {
+ spin_lock_irqsave(&kcov->lock, flags);
+ kcov_debug("t = %px, kcov->t = %px\n", t, kcov->t);
+ /*
+ * This could be a remote task between kcov_remote_start() and
+ * kcov_remote_stop().
+ * In this case we should print a warning right away, since a
+ * task shouldn't be exiting when it's in a kcov coverage
+ * collection section.
+ *
+ * Otherwise, this should be a task that created a local
+ * kcov instance and hasn't called KCOV_DISABLE.
+ * Make sure that t->kcov->t is consistent.
+ */
+ if (WARN_ON(kcov->remote) || WARN_ON(kcov->t != t)) {
+ spin_unlock_irqrestore(&kcov->lock, flags);
+ return;
+ }
+ /* Just to not leave dangling references behind. */
+ kcov_disable(t, kcov);
spin_unlock_irqrestore(&kcov->lock, flags);
- return;
+ kcov_put(kcov);
+ }
+ kcov = t->kcov_remote;
+ if (kcov) {
+ spin_lock_irqsave(&kcov->lock, flags);
+ kcov_debug("t = %px, kcov->t = %px\n", t, kcov->t);
+ /*
+ * This is a KCOV_REMOTE_ENABLE device, and the task is the
+ * user task which has requested remote coverage collection.
+ * Make sure that t->kcov->t is consistent.
+ */
+ if (WARN_ON(!kcov->remote) || WARN_ON(kcov->t != t)) {
+ spin_unlock_irqrestore(&kcov->lock, flags);
+ return;
+ }
+ /* Just to not leave dangling references behind. */
+ kcov_disable(t, kcov);
+ spin_unlock_irqrestore(&kcov->lock, flags);
+ kcov_put(kcov);
}
- /* Just to not leave dangling references behind. */
- kcov_disable(t, kcov);
- spin_unlock_irqrestore(&kcov->lock, flags);
- kcov_put(kcov);
}
static int kcov_mmap(struct file *filep, struct vm_area_struct *vma)
case KCOV_DISABLE:
/* Disable coverage for the current task. */
unused = arg;
- if (unused != 0 || current->kcov != kcov)
- return -EINVAL;
t = current;
+ if (unused != 0 || (kcov != t->kcov && kcov != t->kcov_remote))
+ return -EINVAL;
if (WARN_ON(kcov->t != t))
return -EINVAL;
kcov_disable(t, kcov);
if (kcov->mode != KCOV_MODE_INIT || !kcov->area)
return -EINVAL;
t = current;
- if (kcov->t != NULL || t->kcov != NULL)
+ if (kcov->t != NULL || t->kcov_remote != NULL)
return -EBUSY;
remote_arg = (struct kcov_remote_arg *)arg;
mode = kcov_get_mode(remote_arg->trace_mode);
LONG_MAX / sizeof(unsigned long))
return -EINVAL;
kcov->mode = mode;
- t->kcov = kcov;
- t->kcov_mode = KCOV_MODE_REMOTE;
+ t->kcov_remote = kcov;
kcov->t = t;
kcov->remote = true;
kcov->remote_size = remote_arg->area_size;