|
| 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