Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kcov: remote coverage #3

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions Documentation/dev-tools/kcov.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Profiling data will only become accessible once debugfs has been mounted::

Coverage collection
-------------------

The following program demonstrates coverage collection from within a test
program using kcov:

Expand Down Expand Up @@ -128,6 +129,7 @@ only need to enable coverage (disable happens automatically on thread end).

Comparison operands collection
------------------------------

Comparison operands collection is similar to coverage collection:

.. code-block:: c
Expand Down Expand Up @@ -202,3 +204,100 @@ Comparison operands collection is similar to coverage collection:

Note that the kcov modes (coverage collection or comparison operands) are
mutually exclusive.

Remote coverage collection
--------------------------

With KCOV_ENABLE coverage is collected only for syscalls that are issued from
the current process. With KCOV_REMOTE_ENABLE it's possible to collect coverage
for arbitrary parts of the kernel code, provided that this part is annotated
with kcov_remote_start/kcov_remote_stop.

This allows to collect coverage from two types of kernel background threads:
the global ones, that are spawned during kernel boot and are always running
(e.g. USB hub_event); and the local ones, that are spawned when a user
interacts with some kernel interfaces (e.g. vhost).

To enable collecting coverage from a global background thread, a unique global
id must be assigned and passed to the corresponding kcov_remote_start annotation
call. Then a userspace process can pass this id to the KCOV_REMOTE_ENABLE ioctl
in the handles array field of the kcov_remote_arg struct. This will attach kcov
device to the code section, that is referenced by this id. Multiple ids can be
targeted with the same kcov device simultaneously.

Since there might be many local background threads spawned from different
userspace processes, we can't use a single global id per annotation. Instead,
the userspace process passes an id through the common_handle field of the
kcov_remote_arg struct. This id gets saved to the kcov_handle field in the
current task_struct and needs to be passed to the newly spawned threads via
custom annotations. Those threads should be in turn annotated with
kcov_remote_start/kcov_remote_stop.

.. code-block:: c

struct kcov_remote_arg {
unsigned trace_mode;
unsigned area_size;
unsigned num_handles;
uint64_t common_handle;
uint64_t handles[0];
};

#define KCOV_REMOTE_MAX_HANDLES 0x10000

#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long)
#define KCOV_ENABLE _IO('c', 100)
#define KCOV_DISABLE _IO('c', 101)
#define KCOV_REMOTE_ENABLE _IOW('c', 102, struct kcov_remote_arg)

#define COVER_SIZE (64 << 10)

#define KCOV_TRACE_PC 0
#define KCOV_TRACE_CMP 1

#define KCOV_REMOTE_ID 0x42

int main(int argc, char **argv)
{
int fd;
unsigned long *cover, n, i;
uint64_t handle;

fd = open("/sys/kernel/debug/kcov", O_RDWR);
if (fd == -1)
perror("open"), exit(1);
if (ioctl(fd, KCOV_INIT_TRACE, COVER_SIZE))
perror("ioctl"), exit(1);
cover = (unsigned long*)mmap(NULL, COVER_SIZE * sizeof(unsigned long),
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if ((void*)cover == MAP_FAILED)
perror("mmap"), exit(1);
/* Enable coverage collection from the USB bus #1. */
arg = calloc(1, sizeof(*arg) + sizeof(uint64_t));
if (!arg)
perror("calloc"), exit(1);
arg->trace_mode = KCOV_TRACE_PC;
arg->area_size = COVER_SIZE;
arg->num_handles = 1;
arg->handles[0] = KCOV_REMOTE_ID;
if (ioctl(fd, KCOV_REMOTE_ENABLE, arg))
perror("ioctl"), free(arg), exit(1);
free(arg);

/*
* The user needs to trigger execution of kernel code section that is
* annotated with KCOV_REMOTE_ID.
*/
sleep(2);

n = __atomic_load_n(&cover[0], __ATOMIC_RELAXED);
for (i = 0; i < n; i++)
printf("0x%lx\n", cover[i + 1]);
if (ioctl(fd, KCOV_DISABLE, 0))
perror("ioctl"), exit(1);
if (munmap(cover, COVER_SIZE * sizeof(unsigned long)))
perror("munmap"), exit(1);
if (close(fd))
perror("close"), exit(1);
return 0;
}
5 changes: 5 additions & 0 deletions drivers/usb/core/hub.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <linux/random.h>
#include <linux/pm_qos.h>
#include <linux/kobject.h>
#include <linux/kcov.h>

#include <linux/uaccess.h>
#include <asm/byteorder.h>
Expand Down Expand Up @@ -5374,6 +5375,8 @@ static void hub_event(struct work_struct *work)
hub_dev = hub->intfdev;
intf = to_usb_interface(hub_dev);

kcov_remote_start(kcov_remote_handle_usb(hdev->bus->busnum));

dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",
hdev->state, hdev->maxchild,
/* NOTE: expects max 15 ports... */
Expand Down Expand Up @@ -5480,6 +5483,8 @@ static void hub_event(struct work_struct *work)
/* Balance the stuff in kick_hub_wq() and allow autosuspend */
usb_autopm_put_interface(intf);
kref_put(&hub->kref, hub_release);

kcov_remote_stop();
}

static const struct usb_device_id hub_id_table[] = {
Expand Down
6 changes: 6 additions & 0 deletions drivers/vhost/vhost.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <linux/sched/signal.h>
#include <linux/interval_tree_generic.h>
#include <linux/nospec.h>
#include <linux/kcov.h>

#include "vhost.h"

Expand Down Expand Up @@ -357,7 +358,9 @@ static int vhost_worker(void *data)
llist_for_each_entry_safe(work, work_next, node, node) {
clear_bit(VHOST_WORK_QUEUED, &work->flags);
__set_current_state(TASK_RUNNING);
kcov_remote_start(dev->kcov_handle);
work->fn(work);
kcov_remote_stop();
if (need_resched())
schedule();
}
Expand Down Expand Up @@ -546,6 +549,7 @@ long vhost_dev_set_owner(struct vhost_dev *dev)

/* No owner, become one */
dev->mm = get_task_mm(current);
dev->kcov_handle = current->kcov_handle;
worker = kthread_create(vhost_worker, dev, "vhost-%d", current->pid);
if (IS_ERR(worker)) {
err = PTR_ERR(worker);
Expand All @@ -571,6 +575,7 @@ long vhost_dev_set_owner(struct vhost_dev *dev)
if (dev->mm)
mmput(dev->mm);
dev->mm = NULL;
dev->kcov_handle = 0;
err_mm:
return err;
}
Expand Down Expand Up @@ -682,6 +687,7 @@ void vhost_dev_cleanup(struct vhost_dev *dev)
if (dev->worker) {
kthread_stop(dev->worker);
dev->worker = NULL;
dev->kcov_handle = 0;
}
if (dev->mm)
mmput(dev->mm);
Expand Down
1 change: 1 addition & 0 deletions drivers/vhost/vhost.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ struct vhost_dev {
int iov_limit;
int weight;
int byte_weight;
u64 kcov_handle;
};

bool vhost_exceeds_weight(struct vhost_virtqueue *vq, int pkts, int total_len);
Expand Down
10 changes: 10 additions & 0 deletions include/linux/kcov.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ enum kcov_mode {
void kcov_task_init(struct task_struct *t);
void kcov_task_exit(struct task_struct *t);

/*
* Reserved handle ranges:
* 0000000000000000 - 0000ffffffffffff : common handles
* 0001000000000000 - 0001ffffffffffff : USB subsystem handles
*/
void kcov_remote_start(u64 handle);
void kcov_remote_stop(void);

#define kcov_prepare_switch(t) \
do { \
(t)->kcov_mode |= KCOV_IN_CTXSW; \
Expand All @@ -41,6 +49,8 @@ do { \

static inline void kcov_task_init(struct task_struct *t) {}
static inline void kcov_task_exit(struct task_struct *t) {}
static inline void kcov_remote_start(u64 handle) {}
static inline void kcov_remote_stop(void) {}
static inline void kcov_prepare_switch(struct task_struct *t) {}
static inline void kcov_finish_switch(struct task_struct *t) {}

Expand Down
6 changes: 6 additions & 0 deletions include/linux/sched.h
Original file line number Diff line number Diff line change
Expand Up @@ -1211,8 +1211,14 @@ struct task_struct {
/* Buffer for coverage collection: */
void *kcov_area;

/* KCOV sequence number: */
int kcov_sequence;

/* KCOV descriptor wired with this task or NULL: */
struct kcov *kcov;

/* KCOV handle for remote coverage collection: */
u64 kcov_handle;
#endif

#ifdef CONFIG_MEMCG
Expand Down
18 changes: 18 additions & 0 deletions include/uapi/linux/kcov.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,20 @@

#include <linux/types.h>

struct kcov_remote_arg {
unsigned int trace_mode;
unsigned int area_size;
unsigned int num_handles;
__u64 common_handle;
__u64 handles[0];
};

#define KCOV_REMOTE_MAX_HANDLES 0x10000

#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long)
#define KCOV_ENABLE _IO('c', 100)
#define KCOV_DISABLE _IO('c', 101)
#define KCOV_REMOTE_ENABLE _IOW('c', 102, struct kcov_remote_arg)

enum {
/*
Expand All @@ -32,4 +43,11 @@ enum {
#define KCOV_CMP_SIZE(n) ((n) << 1)
#define KCOV_CMP_MASK KCOV_CMP_SIZE(3)

#define KCOV_REMOTE_HANDLE_USB 0x0001000000000000ull

static inline __u64 kcov_remote_handle_usb(__u64 bus)
{
return KCOV_REMOTE_HANDLE_USB + bus;
}

#endif /* _LINUX_KCOV_IOCTLS_H */
Loading