Skip to content

Commit 997e05c

Browse files
author
Jean-Romain Garnier
committed
Add drcov TCG plugin
1 parent a132171 commit 997e05c

File tree

2 files changed

+350
-0
lines changed

2 files changed

+350
-0
lines changed

contrib/plugins/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ NAMES += hotpages
1818
NAMES += howvec
1919
NAMES += lockstep
2020
NAMES += hwprofile
21+
NAMES += drcov
2122

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

contrib/plugins/drcov.c

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
/*
2+
* Copyright (C) 2021, Ivanov Arkady <[email protected]>
3+
* Copyright (C) 2023, Jean-Romain Garnier <[email protected]>
4+
*
5+
* Drcov - a DynamoRIO-based tool that collects coverage information
6+
* from a binary. Primary goal this script is to have coverage log
7+
* files that work in Lighthouse.
8+
*
9+
* License: GNU GPL, version 2 or later.
10+
* See the COPYING file in the top-level directory.
11+
*/
12+
13+
#include <inttypes.h>
14+
#include <assert.h>
15+
#include <stdlib.h>
16+
#include <inttypes.h>
17+
#include <string.h>
18+
#include <unistd.h>
19+
#include <stdio.h>
20+
#include <glib.h>
21+
22+
#include <qemu-plugin.h>
23+
#include <selfmap.h>
24+
25+
QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
26+
27+
static FILE *fp;
28+
static const char *file_name = "file.drcov.trace";
29+
static GMutex bb_lock;
30+
static GMutex mod_lock;
31+
32+
typedef struct {
33+
uint32_t start;
34+
uint16_t size;
35+
uint16_t mod_id;
36+
bool exec;
37+
} bb_entry_t;
38+
39+
typedef struct {
40+
uint16_t id;
41+
uint64_t base;
42+
uint64_t end;
43+
uint64_t entry;
44+
gchar* path;
45+
bool loaded;
46+
} module_entry_t;
47+
48+
/* Translated blocks */
49+
static GPtrArray *blocks;
50+
51+
/* Loaded modules */
52+
static GPtrArray *modules;
53+
static uint16_t next_mod_id = 0;
54+
55+
/* Plugin */
56+
57+
static void printf_char_array32(uint32_t data)
58+
{
59+
const uint8_t *bytes = (const uint8_t *)(&data);
60+
fwrite(bytes, sizeof(char), sizeof(data), fp);
61+
}
62+
63+
static void printf_char_array16(uint16_t data)
64+
{
65+
const uint8_t *bytes = (const uint8_t *)(&data);
66+
fwrite(bytes, sizeof(char), sizeof(data), fp);
67+
}
68+
69+
static void printf_mod(gpointer data, gpointer user_data)
70+
{
71+
module_entry_t *mod = (module_entry_t *)data;
72+
fprintf(fp, "%d, 0x%" PRIx64 ", 0x%" PRIx64 ", 0x%" PRIx64 ", %s\n",
73+
mod->id, mod->base, mod->end, mod->entry, mod->path);
74+
g_free(mod);
75+
}
76+
77+
static void printf_bb(gpointer data, gpointer user_data)
78+
{
79+
bb_entry_t *bb = (bb_entry_t *)data;
80+
if (bb->exec) {
81+
printf_char_array32(bb->start);
82+
printf_char_array16(bb->size);
83+
printf_char_array16(bb->mod_id);
84+
}
85+
g_free(bb);
86+
}
87+
88+
static void printf_header(unsigned long count)
89+
{
90+
fprintf(fp, "DRCOV VERSION: 2\n");
91+
fprintf(fp, "DRCOV FLAVOR: drcov-64\n");
92+
fprintf(fp, "Module Table: version 2, count %d\n", modules->len);
93+
fprintf(fp, "Columns: id, base, end, entry, path\n");
94+
g_ptr_array_foreach(modules, printf_mod, NULL);
95+
fprintf(fp, "BB Table: %ld bbs\n", count);
96+
}
97+
98+
static module_entry_t *create_mod_entry(MapInfo *info)
99+
{
100+
module_entry_t *module = g_new0(module_entry_t, 1);
101+
module->id = next_mod_id++;
102+
module->base = info->start;
103+
module->end = info->end;
104+
module->entry = 0;
105+
module->path = g_strdup(info->path);
106+
module->loaded = true;
107+
return module;
108+
}
109+
110+
static guint insert_mod_entry(module_entry_t *module, guint start_idx)
111+
{
112+
module_entry_t *entry;
113+
guint i = start_idx;
114+
guint insert_idx = 0;
115+
116+
// Find where to insert this modules, if it doesn't already exist, so we
117+
// keep the module list sorted
118+
while (i < modules->len) {
119+
entry = (module_entry_t *)modules->pdata[i];
120+
121+
// If the new module ends before the current one starts, insert it here
122+
// to keep the modules array sorted
123+
if (entry->base >= module->end) {
124+
g_ptr_array_insert(modules, i, module);
125+
return i++;
126+
}
127+
128+
// If the new module starts after the current one ends, we'll insert it
129+
// later
130+
if (entry->end <= module->base) {
131+
i++;
132+
continue;
133+
}
134+
135+
// Now, two cases remain: the new module is the same as the current
136+
// entry, or the new module is different but has intersecting addresses
137+
138+
// Start by checking if the two modules match
139+
if (
140+
entry->base == module->base
141+
&& entry->end == module->end
142+
&& !strcmp(entry->path, module->path)
143+
) {
144+
// This module is already in the array, not need to insert it again
145+
entry->loaded = true;
146+
g_free(module);
147+
return i;
148+
}
149+
150+
// We know this is a new module and there is at least one old module
151+
// with intersecting addresses
152+
153+
// Mark all modules which start before the new one as unloaded
154+
// Note: there is no need to check entry->end because of the previous
155+
// checks
156+
while (entry->base < module->base && i < modules->len) {
157+
entry = (module_entry_t *)modules->pdata[i];
158+
entry->loaded = false;
159+
i++;
160+
}
161+
162+
// This is the right place to insert the new module, so save this index
163+
insert_idx = i;
164+
165+
// We still need to mark all the modules which start before the new one
166+
// ends as unloaded
167+
while (entry->base < module->end && i < modules->len) {
168+
entry = (module_entry_t *)modules->pdata[i];
169+
entry->loaded = false;
170+
i++;
171+
}
172+
173+
// Finally, insert the new module
174+
g_ptr_array_insert(modules, insert_idx, module);
175+
return i++;
176+
}
177+
178+
// If nowhere was found to insert the module, simply append it
179+
g_ptr_array_add(modules, module);
180+
return modules->len;
181+
}
182+
183+
static void update_mod_entries(void)
184+
{
185+
guint insert_idx;
186+
module_entry_t *module;
187+
GSList *maps, *iter;
188+
MapInfo *info;
189+
190+
// Read modules from self_maps, which is unfortunately very slow, and insert
191+
// them in our internal array
192+
module = NULL;
193+
insert_idx = 0;
194+
maps = read_self_maps();
195+
for (iter = maps; iter; iter = g_slist_next(iter)) {
196+
info = (MapInfo *)iter->data;
197+
// We want to merge contiguous entries for the same file into a single
198+
// module
199+
if (NULL == module) {
200+
// There is no previous entry, create one and merge it later
201+
module = create_mod_entry(info);
202+
} else if (module->end == info->start && !strcmp(module->path, info->path)) {
203+
// This new entry can be merged with the existing module and
204+
// inserted later
205+
module->end = info->end;
206+
continue;
207+
} else if (strlen(info->path) > 0 && info->path[0] != '[') {
208+
// This is a different entry which also happens to be interesting,
209+
// so insert the previous one and create a new
210+
insert_idx = insert_mod_entry(module, insert_idx);
211+
module = create_mod_entry(info);
212+
}
213+
}
214+
215+
// If there is a module left over, insert it now
216+
if (NULL != module) {
217+
insert_mod_entry(module, insert_idx);
218+
}
219+
220+
free_self_maps(maps);
221+
}
222+
223+
static module_entry_t *get_cached_exec_mod_entry(uint64_t pc)
224+
{
225+
guint i;
226+
module_entry_t *entry;
227+
228+
// Check if this address is contained within one of the modules we already
229+
// know about
230+
for (i = 0; i < modules->len; i++) {
231+
entry = (module_entry_t *)modules->pdata[i];
232+
if (pc >= entry->base && pc < entry->end && entry->loaded) {
233+
return entry;
234+
}
235+
}
236+
return NULL;
237+
}
238+
239+
static module_entry_t *get_exec_mod_entry(uint64_t pc)
240+
{
241+
module_entry_t *module = NULL;
242+
243+
g_mutex_lock(&mod_lock);
244+
245+
// Find module within which pc is contained
246+
// Important: This will not work properly if a module is dynamically loaded
247+
// (e.g. using dlopen), unloaded, and then another is loaded at the same
248+
// address
249+
module = get_cached_exec_mod_entry(pc);
250+
251+
// If none is found, try to reload module list and look again
252+
if (NULL == module) {
253+
update_mod_entries();
254+
module = get_cached_exec_mod_entry(pc);
255+
}
256+
257+
g_mutex_unlock(&mod_lock);
258+
return module;
259+
}
260+
261+
static void count_block(gpointer data, gpointer user_data)
262+
{
263+
unsigned long *count = (unsigned long *) user_data;
264+
bb_entry_t *bb = (bb_entry_t *)data;
265+
if (bb->exec) {
266+
*count = *count + 1;
267+
}
268+
}
269+
270+
static void plugin_exit(qemu_plugin_id_t id, void *p)
271+
{
272+
unsigned long count = 0;
273+
g_mutex_lock(&bb_lock);
274+
g_mutex_lock(&mod_lock);
275+
g_ptr_array_foreach(blocks, count_block, &count);
276+
277+
/* Print function */
278+
printf_header(count);
279+
g_ptr_array_foreach(blocks, printf_bb, NULL);
280+
281+
/* Clear */
282+
g_ptr_array_free(blocks, true);
283+
g_ptr_array_free(modules, true);
284+
285+
fclose(fp);
286+
287+
g_mutex_unlock(&mod_lock);
288+
g_mutex_unlock(&bb_lock);
289+
}
290+
291+
static void plugin_init(void)
292+
{
293+
fp = fopen(file_name, "wb");
294+
blocks = g_ptr_array_sized_new(128);
295+
modules = g_ptr_array_sized_new(16);
296+
}
297+
298+
static void vcpu_tb_exec(unsigned int cpu_index, void *udata)
299+
{
300+
bb_entry_t *bb = (bb_entry_t *) udata;
301+
302+
g_mutex_lock(&bb_lock);
303+
bb->exec = true;
304+
g_mutex_unlock(&bb_lock);
305+
}
306+
307+
static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
308+
{
309+
uint64_t pc = qemu_plugin_tb_vaddr(tb);
310+
size_t n = qemu_plugin_tb_n_insns(tb);
311+
module_entry_t *module = get_exec_mod_entry(pc);
312+
bb_entry_t *bb = g_new0(bb_entry_t, 1);
313+
314+
for (int i = 0; i < n; i++) {
315+
bb->size += qemu_plugin_insn_size(qemu_plugin_tb_get_insn(tb, i));
316+
}
317+
318+
bb->start = module ? (pc - module->base): pc;
319+
bb->mod_id = module ? module->id: -1;
320+
bb->exec = false;
321+
322+
g_mutex_lock(&bb_lock);
323+
g_ptr_array_add(blocks, bb);
324+
g_mutex_unlock(&bb_lock);
325+
326+
qemu_plugin_register_vcpu_tb_exec_cb(tb, vcpu_tb_exec,
327+
QEMU_PLUGIN_CB_NO_REGS,
328+
(void *)bb);
329+
330+
}
331+
332+
QEMU_PLUGIN_EXPORT
333+
int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info,
334+
int argc, char **argv)
335+
{
336+
for (int i = 0; i < argc; i++) {
337+
g_auto(GStrv) tokens = g_strsplit(argv[i], "=", 2);
338+
if (g_strcmp0(tokens[0], "filename") == 0) {
339+
file_name = g_strdup(tokens[1]);
340+
}
341+
}
342+
343+
plugin_init();
344+
345+
qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
346+
qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
347+
348+
return 0;
349+
}

0 commit comments

Comments
 (0)