Add the ability to lookup (enumerate/find) compilation units. From: <> --- chronicle/docs/protocol.html | 28 ++++++++++++ chronicle/query/debug.c | 94 ++++++++++++++++++++++++++++++++++++++-- chronicle/query/debug.h | 9 ++++ chronicle/query/debug_dwarf2.c | 51 ++++++++++++++++++++++ chronicle/query/debug_dwarf2.h | 27 +++++++++++ chronicle/query/query.c | 15 ++++++ 6 files changed, 219 insertions(+), 5 deletions(-) diff --git a/chronicle/docs/protocol.html b/chronicle/docs/protocol.html index 7ebc9e2..8a0db44 100644 --- a/chronicle/docs/protocol.html +++ b/chronicle/docs/protocol.html @@ -282,8 +282,36 @@ which compilationUnit is relative.
  • PLI
  • UPC +
  • compilationUnitBegin: Present when the compilation unit contains +information about the start of its code and is referenced in a mapped context. +(For example, a function object may be a mapped context, but type information +is not.) The value is the address of the start of the compilation unit's +code. Used in conjunction with compilationUnitEnd, it can be used to constrain +an instruction scan to just the code within a given compilation unit. +
  • compilationUnitEnd: The ending address of a compilation unit's +code. See compilationUnitBegin for more information on when it may +be present. +

    Additionally, compilation united can be enumerated or explicitly searched for +by using the lookupCompilationUnits. Two arguments are taken to +filter the set of compilation units returned, debugObjectName and +compilationUnitName. debugObjectName performs a prefix +match against all debug objects known to the system (ex: a.out) with their +full path. compilationUnitName performs a prefix match against all +compilation units whose debug object passed the debugObjectName filtration. +Any results returned will potentially have the above compilation unit fields, +plus a debugObject field specifying the debugObject matched. + +

    For example, let us take a hypothetical binary /tmp/foobin built from +/tmp/foo.c, /tmp/foobar.c, and /tmp/bar.c. We can enumerate all compilation +units known to the query engine (including debug objects other than /tmp/foobin +and their compilation units) by providing empty strings for +debugObjectName and compilationUnitName. If we specify +/tmp/foobin for debugObjectName, all of its compilation units will be +listed. If we then specify 'foo' in compilationUnitName, we will only +get information on foo.c and foobar.c +

    Autocomplete

    The command autocomplete obtains a list of global diff --git a/chronicle/query/debug.c b/chronicle/query/debug.c index 0bf4e70..5bb92cc 100644 --- a/chronicle/query/debug.c +++ b/chronicle/query/debug.c @@ -322,8 +322,12 @@ static int case_sensitive_cmp(const char* c1, const char* c2) { return strcmp(c1, c2); } -static void output_compilation_unit_info(CH_DbgDwarf2CompilationUnitInfo* info, - JSON_Builder* builder) { +/** + * Inject compilation unit information into the current JSON builder object. + */ +static void inject_compilation_unit_info(CH_DbgDwarf2CompilationUnitInfo* info, + CH_DBAddrMapEntry* map_event, + JSON_Builder* builder) { if (info->language) { JSON_append_string(builder, "language", info->language); } @@ -332,9 +336,89 @@ static void output_compilation_unit_info(CH_DbgDwarf2CompilationUnitInfo* info, } if (info->compilation_unit_dir) { JSON_append_string(builder, "compilationUnitDir", info->compilation_unit_dir); - } + } + if (info->low_pc && map_event) { + CH_Address virtual_addr = + info->low_pc - map_event->offset + map_event->address; + + JSON_append_int(builder, "compilationUnitBegin", virtual_addr); + } + if (info->high_pc && map_event) { + CH_Address virtual_addr = + info->high_pc - map_event->offset + map_event->address; + + JSON_append_int(builder, "compilationUnitEnd", virtual_addr); + } } +/** + * Output a top-level JSON object for the given compilation unit belonging to + * the given debug object. + */ +static void output_compilation_unit_info(DebugObject *debug_obj, + CH_DbgDwarf2CompilationUnitInfo* info, + CH_DBAddrMapEntry* map_event, + JSON_Builder* builder) { + JSON_open_object(builder, NULL); + JSON_append_string(builder, "debugObject", debug_obj->name); + inject_compilation_unit_info(info, map_event, builder); + JSON_close_object(builder); +} + +int dbg_lookup_compilation_units(QueryThread *q, JSON_Builder* builder, + const char *do_name, const char *cu_name) { + DebugObject *obj, *stop_obj; + CH_DbgDwarf2Object *dwobj; + CH_DbgDwarf2Offset *cu_offset; + CH_DbgDwarf2CompilationUnitInfo cu_info; + + int do_cmplen, cu_cmplen; + + do_cmplen = strlen(do_name); + cu_cmplen = strlen(cu_name); + + /* iterate over Debug Objects */ + for (obj=debug_objects, stop_obj=debug_objects+debug_object_count; + obj != stop_obj; ++obj) { + dwobj = obj->dwarf_obj; + + if (strncmp(obj->name, do_name, do_cmplen)) + continue; + + /* iterate over compilation units inside */ + cu_offset=NULL; + while (dwarf2_enum_compilation_units(dwobj, &cu_offset) && + cu_offset != NULL) { + if (!dwarf2_lookup_compilation_unit_info(dwobj, + *cu_offset, + &cu_info)) + continue; + + if (!strncmp(cu_info.compilation_unit, cu_name, cu_cmplen)) { + uint32_t i_map_event; + /* If we have low_pc/high_pc information, we want to emit one copy + of the debug object for each time it was mapped. + If we lack the low_pc/high_pc information, emit it just once. */ + if (cu_info.low_pc != 0 && cu_info.high_pc != 0) { + for (i_map_event = 0; i_map_event < obj->num_map_events; ++i_map_event) { + CH_DBAddrMapEntry* entry = get_address_map_entries() + + obj->map_events[i_map_event]; + if ((entry->offset <= cu_info.high_pc) && + (cu_info.low_pc <= (entry->offset + entry->length))) { + output_compilation_unit_info(obj, &cu_info, entry, builder); + } + } + } else { + output_compilation_unit_info(obj, &cu_info, NULL, builder); + } + } + } + } + + return 1; +} + + static void append_value_key(JSON_Builder* builder, const char* field, DebugObject* defining_object, uintptr_t context_offset, uintptr_t offset) { @@ -493,7 +577,7 @@ static void output_function_object(DebugObject* defining_object, JSON_close_array(builder); } - output_compilation_unit_info(&info->cu, builder); + inject_compilation_unit_info(&info->cu, map_event, builder); JSON_close_object(builder); } @@ -1231,7 +1315,7 @@ static int lookup_type_dwarf2(QueryThread* q, JSON_Builder* builder, if (info.is_declaration_only) { JSON_append_simple(builder, "partial", JSON_TRUE); } - output_compilation_unit_info(&info.cu, builder); + inject_compilation_unit_info(&info.cu, NULL, builder); if (info.name) { JSON_append_stringdup(builder, "name", info.name); } diff --git a/chronicle/query/debug.h b/chronicle/query/debug.h index a572683..bcb4b65 100644 --- a/chronicle/query/debug.h +++ b/chronicle/query/debug.h @@ -136,6 +136,15 @@ int dbg_lookup_global_type(QueryThread* q, JSON_Builder* builder, const char* name, const char* namespace_prefix, const char* container_prefix, const char* context_typekey); /** + * Outputs a list of the compilation unit objects belonging to debug objects + * with a name matching do_name and a compilation unit name matching cu_name. + * Empty strings match everything. Note that debug object names include their + * full path, whereas compilation unit names are just the filename. + * Returns false on failure. + */ +int dbg_lookup_compilation_units(QueryThread *q, JSON_Builder* builder, + const char *do_name, const char *cu_name); +/** * Outputs a list of the global function objects for global functions * with this name. */ diff --git a/chronicle/query/debug_dwarf2.c b/chronicle/query/debug_dwarf2.c index 866c0d3..a4b1d11 100644 --- a/chronicle/query/debug_dwarf2.c +++ b/chronicle/query/debug_dwarf2.c @@ -1449,6 +1449,7 @@ static int lookup_compilation_unit_info(CompilationUnitReader* cu_reader, const char* name; const char* comp_dir; int found; + uint64_t addr; if (!language) return 0; @@ -1464,6 +1465,56 @@ static int lookup_compilation_unit_info(CompilationUnitReader* cu_reader, if (!read_attribute_string(&reader, DW_AT_comp_dir, &comp_dir, &found)) return 0; cu_info->compilation_unit_dir = found ? comp_dir : NULL; + + if (!read_attribute_address(&reader, DW_AT_low_pc, &addr, &found)) + cu_info->low_pc = 0; + else if (!translate_dwarf_address_to_file_offset(cu_reader->obj, + addr, + &cu_info->low_pc)) + return 0; + + if (!read_attribute_address(&reader, DW_AT_high_pc, &addr, &found)) + cu_info->high_pc = 0; + else if (!translate_dwarf_address_to_file_offset(cu_reader->obj, + addr, + &cu_info->high_pc)) + return 0; + + return 1; +} + +int dwarf2_enum_compilation_units(CH_DbgDwarf2Object* obj, + CH_DbgDwarf2Offset **cu_offset) { + if (obj == NULL || cu_offset == NULL) + return 0; + + if (*cu_offset == NULL) { + /* only update the iteration pointer if non-empty 'collection' */ + if (obj->num_compilation_units > 0) { + *cu_offset = &obj->debuginfo_compilation_unit_offsets[0]; + } + } else { + /* if we hit the end, zero the iteration pointer to indicate it */ + (*cu_offset)++; + if (*cu_offset == obj->debuginfo_compilation_unit_offsets + + obj->num_compilation_units) { + *cu_offset = NULL; + } + } + return 1; +} + +int dwarf2_lookup_compilation_unit_info(CH_DbgDwarf2Object* obj, + CH_DbgDwarf2Offset cu_offset, + CH_DbgDwarf2CompilationUnitInfo* cu_info) { + CompilationUnitReader cu_reader; + + if (!read_debug_info_header(obj, cu_offset, &cu_reader)) + return 0; + + if (!lookup_compilation_unit_info(&cu_reader, cu_info)) + return 0; + return 1; } diff --git a/chronicle/query/debug_dwarf2.h b/chronicle/query/debug_dwarf2.h index 72073a3..53ffd79 100644 --- a/chronicle/query/debug_dwarf2.h +++ b/chronicle/query/debug_dwarf2.h @@ -95,12 +95,39 @@ int dwarf2_get_source_info(QueryThread* q, CH_DbgDwarf2Object* obj, * Strings describing the compilation unit. */ typedef struct { + CH_Address low_pc; + CH_Address high_pc; /* Receiver does not free any of these, they are immortal */ const char* language; const char* compilation_unit; const char* compilation_unit_dir; } CH_DbgDwarf2CompilationUnitInfo; + +/** + * Enumerate over the compilation units inside of a dwarf2 object offset-wise. + * The cu_offset variable serves as the iterator. When first called, *cu_offset + * should be set to NULL. When the call returns, if the dwarf2 object has any + * compilation units, *cu_offset will be non-NULL and **cu_offset will be a + * valid offset which can be used in a call to + * dwarf2_lookup_compilation_unit_info. cu_offset should be left as-is between + * calls. When there are no more compilation units to process, *cu_offset will + * be NULL. + * + * Returns false on failure. + */ +int dwarf2_enum_compilation_units(CH_DbgDwarf2Object* obj, + CH_DbgDwarf2Offset **cu_offset); + +/** + * Get information about a compilation unit. + * + * Returns false on failure. + */ +int dwarf2_lookup_compilation_unit_info(CH_DbgDwarf2Object* obj, + CH_DbgDwarf2Offset cu_offset, + CH_DbgDwarf2CompilationUnitInfo* cu_info); + /** * Data about a function. */ diff --git a/chronicle/query/query.c b/chronicle/query/query.c index 3829657..77951c0 100644 --- a/chronicle/query/query.c +++ b/chronicle/query/query.c @@ -516,6 +516,20 @@ static void autocomplete_command(QueryThread* q, JSON_Value* v) { safe_free(result.match_names); } +static void lookup_compilation_units_command(QueryThread* q, JSON_Value* v) { + JSON_Value* do_name = check_field_of_type(q, v, "command", "debugObjectName", JSON_STRING); + JSON_Value* cu_name = check_field_of_type(q, v, "command", "compilationUnitName", JSON_STRING); + JSON_Builder builder; + + if (!do_name || !cu_name) + return; + + add_work(q, 1); + JSON_builder_init_array(&builder); + dbg_lookup_compilation_units(q, &builder, do_name->v.s, cu_name->v.s); + complete_work(q, 1, &builder); +} + static void lookup_global_functions_command(QueryThread* q, JSON_Value* v) { JSON_Value* name = check_field_of_type(q, v, "command", "name", JSON_STRING); JSON_Builder builder; @@ -1311,6 +1325,7 @@ static Command commands[] = { {"getLocation", get_location_command}, {"getParameters", get_parameters_command}, {"info", info_command}, + {"lookupCompilationUnits", lookup_compilation_units_command}, {"lookupGlobalFunctions", lookup_global_functions_command}, {"lookupGlobalType", lookup_global_type_command}, {"lookupType", lookup_type_command},