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: collect coverage from remote threads #2

Closed
wants to merge 1 commit 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
100 changes: 100 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,101 @@ 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_REMOTE_ENABLE it's possible to collect coverage from arbitrary
kernel threads. For that the targeted section of kernel code needs to be
annotated with kcov_remote_start(unique_id) and kcov_remote_stop(). Then
ioctl(kcov, KCOV_REMOTE_ENABLE) can be used to make kcov start collecting
coverage from that code section. Multiple ids can be targeted with the same
kcov device simultaneously.

This allows to collect coverage from both 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). Collecting coverage from
both types of kernel threads requires custom annotations.

To collect coverage from a global background thread add kcov_remote_start/
kcov_remote_stop annotation with a unique id to that thread's code and then
pass this id in the handles array field of the kcov_remote_arg struct.

A tracking id for local background threads is passed through the common_handle
field of the kcov_remote_arg struct. This id gets saved to the kcov_handle
field in the thread that created kcov 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 KCOV_REMOTE_HANDLE_USB 0x4242000000000000ull

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

#define COVER_SIZE (64 << 10)

#define KCOV_TRACE_PC 0
#define KCOV_TRACE_CMP 1

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_handle_usb(1);
if (ioctl(fd, KCOV_REMOTE_ENABLE, arg))
perror("ioctl"), free(arg), exit(1);
free(arg);

/* Sleep. The user needs to trigger some activity on the USB bus #1. */
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;
}
4 changes: 4 additions & 0 deletions drivers/usb/core/hub.c
Original file line number Diff line number Diff line change
Expand Up @@ -5330,6 +5330,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 @@ -5436,6 +5438,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
11 changes: 11 additions & 0 deletions drivers/vhost/vhost.c
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,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 @@ -503,6 +505,9 @@ long vhost_dev_set_owner(struct vhost_dev *dev)

/* No owner, become one */
dev->mm = get_task_mm(current);
#ifdef CONFIG_KCOV
dev->kcov_handle = current->kcov_handle;
#endif
worker = kthread_create(vhost_worker, dev, "vhost-%d", current->pid);
if (IS_ERR(worker)) {
err = PTR_ERR(worker);
Expand All @@ -528,6 +533,9 @@ long vhost_dev_set_owner(struct vhost_dev *dev)
if (dev->mm)
mmput(dev->mm);
dev->mm = NULL;
#ifdef CONFIG_KCOV
dev->kcov_handle = 0;
#endif
err_mm:
return err;
}
Expand Down Expand Up @@ -639,6 +647,9 @@ void vhost_dev_cleanup(struct vhost_dev *dev)
if (dev->worker) {
kthread_stop(dev->worker);
dev->worker = NULL;
#ifdef CONFIG_KCOV
dev->kcov_handle = 0;
#endif
}
if (dev->mm)
mmput(dev->mm);
Expand Down
3 changes: 3 additions & 0 deletions drivers/vhost/vhost.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ struct vhost_dev {
struct list_head read_list;
struct list_head pending_list;
wait_queue_head_t wait;
#ifdef CONFIG_KCOV
u64 kcov_handle;
#endif
};

void vhost_dev_init(struct vhost_dev *, struct vhost_virtqueue **vqs, int nvqs);
Expand Down
5 changes: 5 additions & 0 deletions include/linux/kcov.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ enum kcov_mode {
void kcov_task_init(struct task_struct *t);
void kcov_task_exit(struct task_struct *t);

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 +44,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 @@ -1154,8 +1154,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 trace_mode;
unsigned area_size;
unsigned 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 0x4242000000000000ull

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

#endif /* _LINUX_KCOV_IOCTLS_H */
Loading