Skip to content

Commit

Permalink
Merge pull request #56 from JRomainG/master
Browse files Browse the repository at this point in the history
Add ability to generate a drcov trace
  • Loading branch information
vanhauser-thc authored Jan 12, 2024
2 parents 927dba1 + 997e05c commit e63c9af
Show file tree
Hide file tree
Showing 2 changed files with 350 additions and 0 deletions.
1 change: 1 addition & 0 deletions contrib/plugins/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ NAMES += hotpages
NAMES += howvec
NAMES += lockstep
NAMES += hwprofile
NAMES += drcov

SONAMES := $(addsuffix .so,$(addprefix lib,$(NAMES)))

Expand Down
349 changes: 349 additions & 0 deletions contrib/plugins/drcov.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,349 @@
/*
* Copyright (C) 2021, Ivanov Arkady <[email protected]>
* Copyright (C) 2023, Jean-Romain Garnier <[email protected]>
*
* Drcov - a DynamoRIO-based tool that collects coverage information
* from a binary. Primary goal this script is to have coverage log
* files that work in Lighthouse.
*
* License: GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/

#include <inttypes.h>
#include <assert.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <glib.h>

#include <qemu-plugin.h>
#include <selfmap.h>

QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;

static FILE *fp;
static const char *file_name = "file.drcov.trace";
static GMutex bb_lock;
static GMutex mod_lock;

typedef struct {
uint32_t start;
uint16_t size;
uint16_t mod_id;
bool exec;
} bb_entry_t;

typedef struct {
uint16_t id;
uint64_t base;
uint64_t end;
uint64_t entry;
gchar* path;
bool loaded;
} module_entry_t;

/* Translated blocks */
static GPtrArray *blocks;

/* Loaded modules */
static GPtrArray *modules;
static uint16_t next_mod_id = 0;

/* Plugin */

static void printf_char_array32(uint32_t data)
{
const uint8_t *bytes = (const uint8_t *)(&data);
fwrite(bytes, sizeof(char), sizeof(data), fp);
}

static void printf_char_array16(uint16_t data)
{
const uint8_t *bytes = (const uint8_t *)(&data);
fwrite(bytes, sizeof(char), sizeof(data), fp);
}

static void printf_mod(gpointer data, gpointer user_data)
{
module_entry_t *mod = (module_entry_t *)data;
fprintf(fp, "%d, 0x%" PRIx64 ", 0x%" PRIx64 ", 0x%" PRIx64 ", %s\n",
mod->id, mod->base, mod->end, mod->entry, mod->path);
g_free(mod);
}

static void printf_bb(gpointer data, gpointer user_data)
{
bb_entry_t *bb = (bb_entry_t *)data;
if (bb->exec) {
printf_char_array32(bb->start);
printf_char_array16(bb->size);
printf_char_array16(bb->mod_id);
}
g_free(bb);
}

static void printf_header(unsigned long count)
{
fprintf(fp, "DRCOV VERSION: 2\n");
fprintf(fp, "DRCOV FLAVOR: drcov-64\n");
fprintf(fp, "Module Table: version 2, count %d\n", modules->len);
fprintf(fp, "Columns: id, base, end, entry, path\n");
g_ptr_array_foreach(modules, printf_mod, NULL);
fprintf(fp, "BB Table: %ld bbs\n", count);
}

static module_entry_t *create_mod_entry(MapInfo *info)
{
module_entry_t *module = g_new0(module_entry_t, 1);
module->id = next_mod_id++;
module->base = info->start;
module->end = info->end;
module->entry = 0;
module->path = g_strdup(info->path);
module->loaded = true;
return module;
}

static guint insert_mod_entry(module_entry_t *module, guint start_idx)
{
module_entry_t *entry;
guint i = start_idx;
guint insert_idx = 0;

// Find where to insert this modules, if it doesn't already exist, so we
// keep the module list sorted
while (i < modules->len) {
entry = (module_entry_t *)modules->pdata[i];

// If the new module ends before the current one starts, insert it here
// to keep the modules array sorted
if (entry->base >= module->end) {
g_ptr_array_insert(modules, i, module);
return i++;
}

// If the new module starts after the current one ends, we'll insert it
// later
if (entry->end <= module->base) {
i++;
continue;
}

// Now, two cases remain: the new module is the same as the current
// entry, or the new module is different but has intersecting addresses

// Start by checking if the two modules match
if (
entry->base == module->base
&& entry->end == module->end
&& !strcmp(entry->path, module->path)
) {
// This module is already in the array, not need to insert it again
entry->loaded = true;
g_free(module);
return i;
}

// We know this is a new module and there is at least one old module
// with intersecting addresses

// Mark all modules which start before the new one as unloaded
// Note: there is no need to check entry->end because of the previous
// checks
while (entry->base < module->base && i < modules->len) {
entry = (module_entry_t *)modules->pdata[i];
entry->loaded = false;
i++;
}

// This is the right place to insert the new module, so save this index
insert_idx = i;

// We still need to mark all the modules which start before the new one
// ends as unloaded
while (entry->base < module->end && i < modules->len) {
entry = (module_entry_t *)modules->pdata[i];
entry->loaded = false;
i++;
}

// Finally, insert the new module
g_ptr_array_insert(modules, insert_idx, module);
return i++;
}

// If nowhere was found to insert the module, simply append it
g_ptr_array_add(modules, module);
return modules->len;
}

static void update_mod_entries(void)
{
guint insert_idx;
module_entry_t *module;
GSList *maps, *iter;
MapInfo *info;

// Read modules from self_maps, which is unfortunately very slow, and insert
// them in our internal array
module = NULL;
insert_idx = 0;
maps = read_self_maps();
for (iter = maps; iter; iter = g_slist_next(iter)) {
info = (MapInfo *)iter->data;
// We want to merge contiguous entries for the same file into a single
// module
if (NULL == module) {
// There is no previous entry, create one and merge it later
module = create_mod_entry(info);
} else if (module->end == info->start && !strcmp(module->path, info->path)) {
// This new entry can be merged with the existing module and
// inserted later
module->end = info->end;
continue;
} else if (strlen(info->path) > 0 && info->path[0] != '[') {
// This is a different entry which also happens to be interesting,
// so insert the previous one and create a new
insert_idx = insert_mod_entry(module, insert_idx);
module = create_mod_entry(info);
}
}

// If there is a module left over, insert it now
if (NULL != module) {
insert_mod_entry(module, insert_idx);
}

free_self_maps(maps);
}

static module_entry_t *get_cached_exec_mod_entry(uint64_t pc)
{
guint i;
module_entry_t *entry;

// Check if this address is contained within one of the modules we already
// know about
for (i = 0; i < modules->len; i++) {
entry = (module_entry_t *)modules->pdata[i];
if (pc >= entry->base && pc < entry->end && entry->loaded) {
return entry;
}
}
return NULL;
}

static module_entry_t *get_exec_mod_entry(uint64_t pc)
{
module_entry_t *module = NULL;

g_mutex_lock(&mod_lock);

// Find module within which pc is contained
// Important: This will not work properly if a module is dynamically loaded
// (e.g. using dlopen), unloaded, and then another is loaded at the same
// address
module = get_cached_exec_mod_entry(pc);

// If none is found, try to reload module list and look again
if (NULL == module) {
update_mod_entries();
module = get_cached_exec_mod_entry(pc);
}

g_mutex_unlock(&mod_lock);
return module;
}

static void count_block(gpointer data, gpointer user_data)
{
unsigned long *count = (unsigned long *) user_data;
bb_entry_t *bb = (bb_entry_t *)data;
if (bb->exec) {
*count = *count + 1;
}
}

static void plugin_exit(qemu_plugin_id_t id, void *p)
{
unsigned long count = 0;
g_mutex_lock(&bb_lock);
g_mutex_lock(&mod_lock);
g_ptr_array_foreach(blocks, count_block, &count);

/* Print function */
printf_header(count);
g_ptr_array_foreach(blocks, printf_bb, NULL);

/* Clear */
g_ptr_array_free(blocks, true);
g_ptr_array_free(modules, true);

fclose(fp);

g_mutex_unlock(&mod_lock);
g_mutex_unlock(&bb_lock);
}

static void plugin_init(void)
{
fp = fopen(file_name, "wb");
blocks = g_ptr_array_sized_new(128);
modules = g_ptr_array_sized_new(16);
}

static void vcpu_tb_exec(unsigned int cpu_index, void *udata)
{
bb_entry_t *bb = (bb_entry_t *) udata;

g_mutex_lock(&bb_lock);
bb->exec = true;
g_mutex_unlock(&bb_lock);
}

static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
{
uint64_t pc = qemu_plugin_tb_vaddr(tb);
size_t n = qemu_plugin_tb_n_insns(tb);
module_entry_t *module = get_exec_mod_entry(pc);
bb_entry_t *bb = g_new0(bb_entry_t, 1);

for (int i = 0; i < n; i++) {
bb->size += qemu_plugin_insn_size(qemu_plugin_tb_get_insn(tb, i));
}

bb->start = module ? (pc - module->base): pc;
bb->mod_id = module ? module->id: -1;
bb->exec = false;

g_mutex_lock(&bb_lock);
g_ptr_array_add(blocks, bb);
g_mutex_unlock(&bb_lock);

qemu_plugin_register_vcpu_tb_exec_cb(tb, vcpu_tb_exec,
QEMU_PLUGIN_CB_NO_REGS,
(void *)bb);

}

QEMU_PLUGIN_EXPORT
int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info,
int argc, char **argv)
{
for (int i = 0; i < argc; i++) {
g_auto(GStrv) tokens = g_strsplit(argv[i], "=", 2);
if (g_strcmp0(tokens[0], "filename") == 0) {
file_name = g_strdup(tokens[1]);
}
}

plugin_init();

qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);

return 0;
}

0 comments on commit e63c9af

Please sign in to comment.