X-Git-Url: http://git.efficios.com/?p=babeltrace.git;a=blobdiff_plain;f=lib%2Fdebug-info.c;fp=lib%2Fdebug-info.c;h=aa19ce4010aebf5517f1d14d19e7bd5dec7410a2;hp=0000000000000000000000000000000000000000;hb=2748fb3b919e397e5a17ac7b33d3b2027c00ba1f;hpb=5c47fba0fde7ee49e1a0e3f465646643a31afafa diff --git a/lib/debug-info.c b/lib/debug-info.c new file mode 100644 index 00000000..aa19ce40 --- /dev/null +++ b/lib/debug-info.c @@ -0,0 +1,805 @@ +/* + * Babeltrace - Debug Information State Tracker + * + * Copyright (c) 2015 EfficiOS Inc. and Linux Foundation + * Copyright (c) 2015 Philippe Proulx + * Copyright (c) 2015 Antoine Busque + * Copyright (c) 2016 Jérémie Galarneau + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct proc_debug_info_sources { + /* + * Hash table: base address (pointer to uint64_t) to bin info; owned by + * proc_debug_info_sources. + */ + GHashTable *baddr_to_bin_info; + + /* + * Hash table: IP (pointer to uint64_t) to (struct debug_info_source *); + * owned by proc_debug_info_sources. + */ + GHashTable *ip_to_debug_info_src; +}; + +struct debug_info { + /* + * Hash table of VPIDs (pointer to int64_t) to + * (struct ctf_proc_debug_infos*); owned by debug_info. + */ + GHashTable *vpid_to_proc_dbg_info_src; + GQuark q_statedump_bin_info; + GQuark q_statedump_debug_link; + GQuark q_statedump_build_id; + GQuark q_statedump_start; + GQuark q_dl_open; +}; + +static +int debug_info_init(struct debug_info *info) +{ + info->q_statedump_bin_info = g_quark_from_string( + "lttng_ust_statedump:bin_info"); + info->q_statedump_debug_link = g_quark_from_string( + "lttng_ust_statedump:debug_link)"); + info->q_statedump_build_id = g_quark_from_string( + "lttng_ust_statedump:build_id"); + info->q_statedump_start = g_quark_from_string( + "lttng_ust_statedump:start"); + info->q_dl_open = g_quark_from_string("lttng_ust_dl:dlopen"); + + return bin_info_init(); +} + +static +void debug_info_source_destroy(struct debug_info_source *debug_info_src) +{ + if (!debug_info_src) { + return; + } + + free(debug_info_src->func); + free(debug_info_src->src_path); + free(debug_info_src->bin_path); + free(debug_info_src->bin_loc); + g_free(debug_info_src); +} + +static +struct debug_info_source *debug_info_source_create_from_bin(struct bin_info *bin, + uint64_t ip) +{ + int ret; + struct debug_info_source *debug_info_src = NULL; + struct source_location *src_loc = NULL; + + debug_info_src = g_new0(struct debug_info_source, 1); + + if (!debug_info_src) { + goto end; + } + + /* Lookup function name */ + ret = bin_info_lookup_function_name(bin, ip, &debug_info_src->func); + if (ret) { + goto error; + } + + /* Can't retrieve src_loc from ELF, or could not find binary, skip. */ + if (!bin->is_elf_only || !debug_info_src->func) { + /* Lookup source location */ + ret = bin_info_lookup_source_location(bin, ip, &src_loc); + printf_verbose("Failed to lookup source location (err: %i)\n", ret); + } + + if (src_loc) { + debug_info_src->line_no = src_loc->line_no; + + if (src_loc->filename) { + debug_info_src->src_path = strdup(src_loc->filename); + if (!debug_info_src->src_path) { + goto error; + } + + debug_info_src->short_src_path = get_filename_from_path( + debug_info_src->src_path); + } + + source_location_destroy(src_loc); + } + + if (bin->elf_path) { + debug_info_src->bin_path = strdup(bin->elf_path); + if (!debug_info_src->bin_path) { + goto error; + } + + debug_info_src->short_bin_path = get_filename_from_path( + debug_info_src->bin_path); + + ret = bin_info_get_bin_loc(bin, ip, &(debug_info_src->bin_loc)); + if (ret) { + goto error; + } + } + +end: + return debug_info_src; + +error: + debug_info_source_destroy(debug_info_src); + return NULL; +} + +static +void proc_debug_info_sources_destroy( + struct proc_debug_info_sources *proc_dbg_info_src) +{ + if (!proc_dbg_info_src) { + return; + } + + if (proc_dbg_info_src->baddr_to_bin_info) { + g_hash_table_destroy(proc_dbg_info_src->baddr_to_bin_info); + } + + if (proc_dbg_info_src->ip_to_debug_info_src) { + g_hash_table_destroy(proc_dbg_info_src->ip_to_debug_info_src); + } + + g_free(proc_dbg_info_src); +} + +static +struct proc_debug_info_sources *proc_debug_info_sources_create(void) +{ + struct proc_debug_info_sources *proc_dbg_info_src = NULL; + + proc_dbg_info_src = g_new0(struct proc_debug_info_sources, 1); + if (!proc_dbg_info_src) { + goto end; + } + + proc_dbg_info_src->baddr_to_bin_info = g_hash_table_new_full( + g_int64_hash, g_int64_equal, (GDestroyNotify) g_free, + (GDestroyNotify) bin_info_destroy); + if (!proc_dbg_info_src->baddr_to_bin_info) { + goto error; + } + + proc_dbg_info_src->ip_to_debug_info_src = g_hash_table_new_full( + g_int64_hash, g_int64_equal, (GDestroyNotify) g_free, + (GDestroyNotify) debug_info_source_destroy); + if (!proc_dbg_info_src->ip_to_debug_info_src) { + goto error; + } + +end: + return proc_dbg_info_src; + +error: + proc_debug_info_sources_destroy(proc_dbg_info_src); + return NULL; +} + +static +struct proc_debug_info_sources *proc_debug_info_sources_ht_get_entry( + GHashTable *ht, int64_t vpid) +{ + gpointer key = g_new0(int64_t, 1); + struct proc_debug_info_sources *proc_dbg_info_src = NULL; + + if (!key) { + goto end; + } + + *((int64_t *) key) = vpid; + + /* Exists? Return it */ + proc_dbg_info_src = g_hash_table_lookup(ht, key); + if (proc_dbg_info_src) { + goto end; + } + + /* Otherwise, create and return it */ + proc_dbg_info_src = proc_debug_info_sources_create(); + if (!proc_dbg_info_src) { + goto end; + } + + g_hash_table_insert(ht, key, proc_dbg_info_src); + /* Ownership passed to ht */ + key = NULL; +end: + g_free(key); + return proc_dbg_info_src; +} + +static +struct debug_info_source *proc_debug_info_sources_get_entry( + struct proc_debug_info_sources *proc_dbg_info_src, uint64_t ip) +{ + struct debug_info_source *debug_info_src = NULL; + gpointer key = g_new0(uint64_t, 1); + GHashTableIter iter; + gpointer baddr, value; + + if (!key) { + goto end; + } + + *((uint64_t *) key) = ip; + + /* Look in IP to debug infos hash table first. */ + debug_info_src = g_hash_table_lookup( + proc_dbg_info_src->ip_to_debug_info_src, + key); + if (debug_info_src) { + goto end; + } + + /* Check in all bin_infos. */ + g_hash_table_iter_init(&iter, proc_dbg_info_src->baddr_to_bin_info); + + while (g_hash_table_iter_next(&iter, &baddr, &value)) + { + struct bin_info *bin = value; + + if (!bin_info_has_address(value, ip)) { + continue; + } + + /* + * Found; add it to cache. + * + * FIXME: this should be bounded in size (and implement + * a caching policy), and entries should be prunned when + * libraries are unmapped. + */ + debug_info_src = debug_info_source_create_from_bin(bin, ip); + if (debug_info_src) { + g_hash_table_insert( + proc_dbg_info_src->ip_to_debug_info_src, + key, debug_info_src); + /* Ownership passed to ht. */ + key = NULL; + } + break; + } + +end: + free(key); + return debug_info_src; +} + +BT_HIDDEN +struct debug_info_source *debug_info_query(struct debug_info *debug_info, + int64_t vpid, uint64_t ip) +{ + struct debug_info_source *dbg_info_src = NULL; + struct proc_debug_info_sources *proc_dbg_info_src; + + proc_dbg_info_src = proc_debug_info_sources_ht_get_entry( + debug_info->vpid_to_proc_dbg_info_src, vpid); + if (!proc_dbg_info_src) { + goto end; + } + + dbg_info_src = proc_debug_info_sources_get_entry( + proc_dbg_info_src, ip); + if (!dbg_info_src) { + goto end; + } + +end: + return dbg_info_src; +} + +BT_HIDDEN +struct debug_info *debug_info_create(void) +{ + int ret; + struct debug_info *debug_info; + + debug_info = g_new0(struct debug_info, 1); + if (!debug_info) { + goto end; + } + + debug_info->vpid_to_proc_dbg_info_src = g_hash_table_new_full( + g_int64_hash, g_int64_equal, (GDestroyNotify) g_free, + (GDestroyNotify) proc_debug_info_sources_destroy); + if (!debug_info->vpid_to_proc_dbg_info_src) { + goto error; + } + + ret = debug_info_init(debug_info); + if (ret) { + goto error; + } + +end: + return debug_info; +error: + g_free(debug_info); + return NULL; +} + +BT_HIDDEN +void debug_info_destroy(struct debug_info *debug_info) +{ + if (!debug_info) { + goto end; + } + + if (debug_info->vpid_to_proc_dbg_info_src) { + g_hash_table_destroy(debug_info->vpid_to_proc_dbg_info_src); + } + + g_free(debug_info); +end: + return; +} + +static +void handle_statedump_build_id_event(struct debug_info *debug_info, + struct ctf_event_definition *event_def) +{ + struct proc_debug_info_sources *proc_dbg_info_src; + struct bt_definition *event_fields_def = NULL; + struct bt_definition *sec_def = NULL; + struct bt_definition *baddr_def = NULL; + struct bt_definition *vpid_def = NULL; + struct bt_definition *build_id_def = NULL; + struct definition_sequence *build_id_seq; + struct bin_info *bin = NULL; + int i; + int64_t vpid; + uint64_t baddr; + uint8_t *build_id = NULL; + uint64_t build_id_len; + + event_fields_def = (struct bt_definition *) event_def->event_fields; + sec_def = (struct bt_definition *) + event_def->stream->stream_event_context; + + if (!event_fields_def || !sec_def) { + goto end; + } + + baddr_def = bt_lookup_definition(event_fields_def, "_baddr"); + if (!baddr_def) { + goto end; + } + + vpid_def = bt_lookup_definition(sec_def, "_vpid"); + if (!vpid_def) { + goto end; + } + + build_id_def = bt_lookup_definition(event_fields_def, "_build_id"); + if (!build_id_def) { + goto end; + } + + if (baddr_def->declaration->id != BT_CTF_TYPE_ID_INTEGER) { + goto end; + } + + if (vpid_def->declaration->id != BT_CTF_TYPE_ID_INTEGER) { + goto end; + } + + if (build_id_def->declaration->id != BT_CTF_TYPE_ID_SEQUENCE) { + goto end; + } + + baddr = bt_get_unsigned_int(baddr_def); + vpid = bt_get_signed_int(vpid_def); + build_id_seq = container_of(build_id_def, + struct definition_sequence, p); + build_id_len = build_id_seq->length->value._unsigned; + + build_id = g_malloc(build_id_len); + if (!build_id) { + goto end; + } + + for (i = 0; i < build_id_len; ++i) { + struct bt_definition **field; + + field = (struct bt_definition **) &g_ptr_array_index( + build_id_seq->elems, i); + build_id[i] = bt_get_unsigned_int(*field); + } + + proc_dbg_info_src = proc_debug_info_sources_ht_get_entry( + debug_info->vpid_to_proc_dbg_info_src, vpid); + if (!proc_dbg_info_src) { + goto end; + } + + bin = g_hash_table_lookup(proc_dbg_info_src->baddr_to_bin_info, + (gpointer) &baddr); + if (!bin) { + /* + * The build_id event comes after the bin has been + * created. If it isn't found, just ignore this event. + */ + goto end; + } + + bin_info_set_build_id(bin, build_id, build_id_len); + +end: + free(build_id); + return; +} + +static +void handle_statedump_debug_link_event(struct debug_info *debug_info, + struct ctf_event_definition *event_def) +{ + struct proc_debug_info_sources *proc_dbg_info_src; + struct bt_definition *event_fields_def = NULL; + struct bt_definition *sec_def = NULL; + struct bt_definition *baddr_def = NULL; + struct bt_definition *vpid_def = NULL; + struct bt_definition *filename_def = NULL; + struct bt_definition *crc32_def = NULL; + struct bin_info *bin = NULL; + int64_t vpid; + uint64_t baddr; + char *filename = NULL; + uint32_t crc32; + + event_fields_def = (struct bt_definition *) event_def->event_fields; + sec_def = (struct bt_definition *) + event_def->stream->stream_event_context; + + if (!event_fields_def || !sec_def) { + goto end; + } + + baddr_def = bt_lookup_definition(event_fields_def, "_baddr"); + if (!baddr_def) { + goto end; + } + + vpid_def = bt_lookup_definition(sec_def, "_vpid"); + if (!vpid_def) { + goto end; + } + + filename_def = bt_lookup_definition(event_fields_def, "_filename"); + if (!filename_def) { + goto end; + } + + crc32_def = bt_lookup_definition(event_fields_def, "_crc32"); + if (!crc32_def) { + goto end; + } + + if (baddr_def->declaration->id != BT_CTF_TYPE_ID_INTEGER) { + goto end; + } + + if (vpid_def->declaration->id != BT_CTF_TYPE_ID_INTEGER) { + goto end; + } + + if (filename_def->declaration->id != BT_CTF_TYPE_ID_STRING) { + goto end; + } + + if (crc32_def->declaration->id != BT_CTF_TYPE_ID_INTEGER) { + goto end; + } + + baddr = bt_get_unsigned_int(baddr_def); + vpid = bt_get_signed_int(vpid_def); + + proc_dbg_info_src = proc_debug_info_sources_ht_get_entry( + debug_info->vpid_to_proc_dbg_info_src, vpid); + if (!proc_dbg_info_src) { + goto end; + } + + bin = g_hash_table_lookup(proc_dbg_info_src->baddr_to_bin_info, + (gpointer) &baddr); + if (!bin) { + /* + * The debug_link event comes after the bin has been + * created. If it isn't found, just ignore this event. + */ + goto end; + } + + filename = bt_get_string(filename_def); + crc32 = bt_get_unsigned_int(crc32_def); + + bin_info_set_debug_link(bin, filename, crc32); + +end: + return; +} + +static +void handle_bin_info_event(struct debug_info *debug_info, + struct ctf_event_definition *event_def, bool has_pic_field) +{ + struct bt_definition *baddr_def = NULL; + struct bt_definition *memsz_def = NULL; + struct bt_definition *path_def = NULL; + struct bt_definition *is_pic_def = NULL; + struct bt_definition *vpid_def = NULL; + struct bt_definition *event_fields_def = NULL; + struct bt_definition *sec_def = NULL; + struct proc_debug_info_sources *proc_dbg_info_src; + struct bin_info *bin; + uint64_t baddr, memsz; + int64_t vpid; + const char *path; + gpointer key = NULL; + bool is_pic; + + event_fields_def = (struct bt_definition *) event_def->event_fields; + sec_def = (struct bt_definition *) + event_def->stream->stream_event_context; + + if (!event_fields_def || !sec_def) { + goto end; + } + + baddr_def = bt_lookup_definition(event_fields_def, "_baddr"); + if (!baddr_def) { + goto end; + } + + memsz_def = bt_lookup_definition(event_fields_def, "_memsz"); + if (!memsz_def) { + goto end; + } + + path_def = bt_lookup_definition(event_fields_def, "_path"); + if (!path_def) { + goto end; + } + + if (has_pic_field) { + is_pic_def = bt_lookup_definition(event_fields_def, "_is_pic"); + if (!is_pic_def) { + goto end; + } + + if (is_pic_def->declaration->id != BT_CTF_TYPE_ID_INTEGER) { + goto end; + } + + is_pic = (bt_get_unsigned_int(is_pic_def) == 1); + } else { + /* + * dlopen has no is_pic field, because the shared + * object is always PIC. + */ + is_pic = true; + } + + vpid_def = bt_lookup_definition(sec_def, "_vpid"); + if (!vpid_def) { + goto end; + } + + if (baddr_def->declaration->id != BT_CTF_TYPE_ID_INTEGER) { + goto end; + } + + if (memsz_def->declaration->id != BT_CTF_TYPE_ID_INTEGER) { + goto end; + } + + if (path_def->declaration->id != BT_CTF_TYPE_ID_STRING) { + goto end; + } + + if (vpid_def->declaration->id != BT_CTF_TYPE_ID_INTEGER) { + goto end; + } + + baddr = bt_get_unsigned_int(baddr_def); + memsz = bt_get_unsigned_int(memsz_def); + path = bt_get_string(path_def); + vpid = bt_get_signed_int(vpid_def); + + if (!path) { + goto end; + } + + if (memsz == 0) { + /* Ignore VDSO. */ + goto end; + } + + proc_dbg_info_src = proc_debug_info_sources_ht_get_entry( + debug_info->vpid_to_proc_dbg_info_src, vpid); + if (!proc_dbg_info_src) { + goto end; + } + + key = g_new0(uint64_t, 1); + if (!key) { + goto end; + } + + *((uint64_t *) key) = baddr; + + bin = g_hash_table_lookup(proc_dbg_info_src->baddr_to_bin_info, + key); + if (bin) { + goto end; + } + + bin = bin_info_create(path, baddr, memsz, is_pic); + if (!bin) { + goto end; + } + + g_hash_table_insert(proc_dbg_info_src->baddr_to_bin_info, + key, bin); + /* Ownership passed to ht. */ + key = NULL; + +end: + g_free(key); + return; +} + +static inline +void handle_statedump_bin_info_event(struct debug_info *debug_info, + struct ctf_event_definition *event_def) +{ + handle_bin_info_event(debug_info, event_def, true); +} + +static inline +void handle_dlopen_event(struct debug_info *debug_info, + struct ctf_event_definition *event_def) +{ + handle_bin_info_event(debug_info, event_def, false); +} + + +static +void handle_statedump_start(struct debug_info *debug_info, + struct ctf_event_definition *event_def) +{ + struct bt_definition *vpid_def = NULL; + struct bt_definition *sec_def = NULL; + struct proc_debug_info_sources *proc_dbg_info_src; + int64_t vpid; + + sec_def = (struct bt_definition *) + event_def->stream->stream_event_context; + if (!sec_def) { + goto end; + } + + vpid_def = bt_lookup_definition(sec_def, "_vpid"); + if (!vpid_def) { + goto end; + } + + vpid = bt_get_signed_int(vpid_def); + + proc_dbg_info_src = proc_debug_info_sources_ht_get_entry( + debug_info->vpid_to_proc_dbg_info_src, vpid); + if (!proc_dbg_info_src) { + goto end; + } + + g_hash_table_remove_all(proc_dbg_info_src->baddr_to_bin_info); + g_hash_table_remove_all(proc_dbg_info_src->ip_to_debug_info_src); + +end: + return; +} + +static +void register_event_debug_infos(struct debug_info *debug_info, + struct ctf_event_definition *event) +{ + struct bt_definition *ip_def, *vpid_def; + int64_t vpid; + uint64_t ip; + struct bt_definition *sec_def; + + /* Get stream event context definition. */ + sec_def = (struct bt_definition *) event->stream->stream_event_context; + if (!sec_def) { + goto end; + } + + /* Get "ip" and "vpid" definitions. */ + vpid_def = bt_lookup_definition((struct bt_definition *) sec_def, + "_vpid"); + ip_def = bt_lookup_definition((struct bt_definition *) sec_def, "_ip"); + + if (!vpid_def || !ip_def) { + goto end; + } + + vpid = bt_get_signed_int(vpid_def); + ip = bt_get_unsigned_int(ip_def); + + /* Get debug info for this context. */ + ((struct definition_integer *) ip_def)->debug_info_src = + debug_info_query(debug_info, vpid, ip); + +end: + return; +} + +BT_HIDDEN +void debug_info_handle_event(struct debug_info *debug_info, + struct ctf_event_definition *event) +{ + struct ctf_event_declaration *event_class; + struct ctf_stream_declaration *stream_class; + + if (!debug_info || !event) { + goto end; + } + + stream_class = event->stream->stream_class; + event_class = g_ptr_array_index(stream_class->events_by_id, + event->stream->event_id); + + if (event_class->name == debug_info->q_statedump_bin_info) { + /* State dump */ + handle_statedump_bin_info_event(debug_info, event); + } else if (event_class->name == debug_info->q_dl_open) { + handle_dlopen_event(debug_info, event); + } else if (event_class->name == debug_info->q_statedump_start) { + /* Start state dump */ + handle_statedump_start(debug_info, event); + } else if (event_class->name == debug_info->q_statedump_debug_link) { + /* Debug link info */ + handle_statedump_debug_link_event(debug_info, event); + } else if (event_class->name == debug_info->q_statedump_build_id) { + /* Build ID info */ + handle_statedump_build_id_event(debug_info, event); + } else { + /* Other events: register debug infos */ + register_event_debug_infos(debug_info, event); + } + +end: + return; +}