summaryrefslogtreecommitdiffstats
path: root/tools/perf/util/dwarf-aux.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/perf/util/dwarf-aux.c')
-rw-r--r--tools/perf/util/dwarf-aux.c240
1 files changed, 230 insertions, 10 deletions
diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index f93e57e2fc..44ef968a7a 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -9,6 +9,7 @@
#include <stdlib.h>
#include "debug.h"
#include "dwarf-aux.h"
+#include "dwarf-regs.h"
#include "strbuf.h"
#include "string2.h"
@@ -696,6 +697,49 @@ Dwarf_Die *die_find_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr,
return die_mem;
}
+static int __die_find_func_rettype_cb(Dwarf_Die *die_mem, void *data)
+{
+ const char *func_name;
+
+ if (dwarf_tag(die_mem) != DW_TAG_subprogram)
+ return DIE_FIND_CB_SIBLING;
+
+ func_name = dwarf_diename(die_mem);
+ if (func_name && !strcmp(func_name, data))
+ return DIE_FIND_CB_END;
+
+ return DIE_FIND_CB_SIBLING;
+}
+
+/**
+ * die_find_func_rettype - Search a return type of function
+ * @cu_die: a CU DIE
+ * @name: target function name
+ * @die_mem: a buffer for result DIE
+ *
+ * Search a non-inlined function which matches to @name and stores the
+ * return type of the function to @die_mem and returns it if found.
+ * Returns NULL if failed. Note that it doesn't needs to find a
+ * definition of the function, so it doesn't match with address.
+ * Most likely, it can find a declaration at the top level. Thus the
+ * callback function continues to sibling entries only.
+ */
+Dwarf_Die *die_find_func_rettype(Dwarf_Die *cu_die, const char *name,
+ Dwarf_Die *die_mem)
+{
+ Dwarf_Die tmp_die;
+
+ cu_die = die_find_child(cu_die, __die_find_func_rettype_cb,
+ (void *)name, &tmp_die);
+ if (!cu_die)
+ return NULL;
+
+ if (die_get_real_type(&tmp_die, die_mem) == NULL)
+ return NULL;
+
+ return die_mem;
+}
+
struct __instance_walk_param {
void *addr;
int (*callback)(Dwarf_Die *, void *);
@@ -1066,8 +1110,10 @@ int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf)
const char *tmp = "";
tag = dwarf_tag(type_die);
- if (tag == DW_TAG_array_type || tag == DW_TAG_pointer_type)
+ if (tag == DW_TAG_pointer_type)
tmp = "*";
+ else if (tag == DW_TAG_array_type)
+ tmp = "[]";
else if (tag == DW_TAG_subroutine_type) {
/* Function pointer */
return strbuf_add(buf, "(function_type)", 15);
@@ -1147,6 +1193,8 @@ static int reg_from_dwarf_op(Dwarf_Op *op)
case DW_OP_regx:
case DW_OP_bregx:
return op->number;
+ case DW_OP_fbreg:
+ return DWARF_REG_FB;
default:
break;
}
@@ -1160,6 +1208,7 @@ static int offset_from_dwarf_op(Dwarf_Op *op)
case DW_OP_regx:
return 0;
case DW_OP_breg0 ... DW_OP_breg31:
+ case DW_OP_fbreg:
return op->number;
case DW_OP_bregx:
return op->number2;
@@ -1353,6 +1402,9 @@ static bool match_var_offset(Dwarf_Die *die_mem, struct find_var_data *data,
return true;
}
+ if (addr_offset < addr_type)
+ return false;
+
if (die_get_real_type(die_mem, &type_die) == NULL)
return false;
@@ -1399,7 +1451,6 @@ static int __die_find_var_reg_cb(Dwarf_Die *die_mem, void *arg)
/* Local variables accessed using frame base register */
if (data->is_fbreg && ops->atom == DW_OP_fbreg &&
- data->offset >= (int)ops->number &&
check_allowed_ops(ops, nops) &&
match_var_offset(die_mem, data, data->offset, ops->number,
/*is_pointer=*/false))
@@ -1490,9 +1541,6 @@ static int __die_find_var_addr_cb(Dwarf_Die *die_mem, void *arg)
if (ops->atom != DW_OP_addr)
continue;
- if (data->addr < ops->number)
- continue;
-
if (check_allowed_ops(ops, nops) &&
match_var_offset(die_mem, data, data->addr, ops->number,
/*is_pointer=*/false))
@@ -1504,7 +1552,6 @@ static int __die_find_var_addr_cb(Dwarf_Die *die_mem, void *arg)
/**
* die_find_variable_by_addr - Find variable located at given address
* @sc_die: a scope DIE
- * @pc: the program address to find
* @addr: the data address to find
* @die_mem: a buffer to save the resulting DIE
* @offset: the offset in the resulting type
@@ -1512,12 +1559,10 @@ static int __die_find_var_addr_cb(Dwarf_Die *die_mem, void *arg)
* Find the variable DIE located at the given address (in PC-relative mode).
* This is usually for global variables.
*/
-Dwarf_Die *die_find_variable_by_addr(Dwarf_Die *sc_die, Dwarf_Addr pc,
- Dwarf_Addr addr, Dwarf_Die *die_mem,
- int *offset)
+Dwarf_Die *die_find_variable_by_addr(Dwarf_Die *sc_die, Dwarf_Addr addr,
+ Dwarf_Die *die_mem, int *offset)
{
struct find_var_data data = {
- .pc = pc,
.addr = addr,
};
Dwarf_Die *result;
@@ -1587,6 +1632,68 @@ void die_collect_vars(Dwarf_Die *sc_die, struct die_var_type **var_types)
die_find_child(sc_die, __die_collect_vars_cb, (void *)var_types, &die_mem);
}
+
+static int __die_collect_global_vars_cb(Dwarf_Die *die_mem, void *arg)
+{
+ struct die_var_type **var_types = arg;
+ Dwarf_Die type_die;
+ int tag = dwarf_tag(die_mem);
+ Dwarf_Attribute attr;
+ Dwarf_Addr base, start, end;
+ Dwarf_Op *ops;
+ size_t nops;
+ struct die_var_type *vt;
+
+ if (tag != DW_TAG_variable)
+ return DIE_FIND_CB_SIBLING;
+
+ if (dwarf_attr(die_mem, DW_AT_location, &attr) == NULL)
+ return DIE_FIND_CB_SIBLING;
+
+ /* Only collect the location with an absolute address. */
+ if (dwarf_getlocations(&attr, 0, &base, &start, &end, &ops, &nops) <= 0)
+ return DIE_FIND_CB_SIBLING;
+
+ if (ops->atom != DW_OP_addr)
+ return DIE_FIND_CB_SIBLING;
+
+ if (!check_allowed_ops(ops, nops))
+ return DIE_FIND_CB_SIBLING;
+
+ if (die_get_real_type(die_mem, &type_die) == NULL)
+ return DIE_FIND_CB_SIBLING;
+
+ vt = malloc(sizeof(*vt));
+ if (vt == NULL)
+ return DIE_FIND_CB_END;
+
+ vt->die_off = dwarf_dieoffset(&type_die);
+ vt->addr = ops->number;
+ vt->reg = -1;
+ vt->offset = 0;
+ vt->next = *var_types;
+ *var_types = vt;
+
+ return DIE_FIND_CB_SIBLING;
+}
+
+/**
+ * die_collect_global_vars - Save all global variables
+ * @cu_die: a CU DIE
+ * @var_types: a pointer to save the resulting list
+ *
+ * Save all global variables in the @cu_die and save them to @var_types.
+ * The @var_types is a singly-linked list containing type and location info.
+ * Actual type can be retrieved using dwarf_offdie() with 'die_off' later.
+ *
+ * Callers should free @var_types.
+ */
+void die_collect_global_vars(Dwarf_Die *cu_die, struct die_var_type **var_types)
+{
+ Dwarf_Die die_mem;
+
+ die_find_child(cu_die, __die_collect_global_vars_cb, (void *)var_types, &die_mem);
+}
#endif /* HAVE_DWARF_GETLOCATIONS_SUPPORT */
#ifdef HAVE_DWARF_CFI_SUPPORT
@@ -1855,3 +1962,116 @@ int die_get_scopes(Dwarf_Die *cu_die, Dwarf_Addr pc, Dwarf_Die **scopes)
*scopes = data.scopes;
return data.nr;
}
+
+static int __die_find_member_offset_cb(Dwarf_Die *die_mem, void *arg)
+{
+ Dwarf_Die type_die;
+ Dwarf_Word size, loc;
+ Dwarf_Word offset = (long)arg;
+ int tag = dwarf_tag(die_mem);
+
+ if (tag != DW_TAG_member)
+ return DIE_FIND_CB_SIBLING;
+
+ /* Unions might not have location */
+ if (die_get_data_member_location(die_mem, &loc) < 0)
+ loc = 0;
+
+ if (offset == loc)
+ return DIE_FIND_CB_END;
+
+ if (die_get_real_type(die_mem, &type_die) == NULL) {
+ // TODO: add a pr_debug_dtp() later for this unlikely failure
+ return DIE_FIND_CB_SIBLING;
+ }
+
+ if (dwarf_aggregate_size(&type_die, &size) < 0)
+ size = 0;
+
+ if (loc < offset && offset < (loc + size))
+ return DIE_FIND_CB_END;
+
+ return DIE_FIND_CB_SIBLING;
+}
+
+/**
+ * die_get_member_type - Return type info of struct member
+ * @type_die: a type DIE
+ * @offset: offset in the type
+ * @die_mem: a buffer to save the resulting DIE
+ *
+ * This function returns a type of a member in @type_die where it's located at
+ * @offset if it's a struct. For now, it just returns the first matching
+ * member in a union. For other types, it'd return the given type directly
+ * if it's within the size of the type or NULL otherwise.
+ */
+Dwarf_Die *die_get_member_type(Dwarf_Die *type_die, int offset,
+ Dwarf_Die *die_mem)
+{
+ Dwarf_Die *member;
+ Dwarf_Die mb_type;
+ int tag;
+
+ tag = dwarf_tag(type_die);
+ /* If it's not a compound type, return the type directly */
+ if (tag != DW_TAG_structure_type && tag != DW_TAG_union_type) {
+ Dwarf_Word size;
+
+ if (dwarf_aggregate_size(type_die, &size) < 0)
+ size = 0;
+
+ if ((unsigned)offset >= size)
+ return NULL;
+
+ *die_mem = *type_die;
+ return die_mem;
+ }
+
+ mb_type = *type_die;
+ /* TODO: Handle union types better? */
+ while (tag == DW_TAG_structure_type || tag == DW_TAG_union_type) {
+ member = die_find_child(&mb_type, __die_find_member_offset_cb,
+ (void *)(long)offset, die_mem);
+ if (member == NULL)
+ return NULL;
+
+ if (die_get_real_type(member, &mb_type) == NULL)
+ return NULL;
+
+ tag = dwarf_tag(&mb_type);
+
+ if (tag == DW_TAG_structure_type || tag == DW_TAG_union_type) {
+ Dwarf_Word loc;
+
+ /* Update offset for the start of the member struct */
+ if (die_get_data_member_location(member, &loc) == 0)
+ offset -= loc;
+ }
+ }
+ *die_mem = mb_type;
+ return die_mem;
+}
+
+/**
+ * die_deref_ptr_type - Return type info for pointer access
+ * @ptr_die: a pointer type DIE
+ * @offset: access offset for the pointer
+ * @die_mem: a buffer to save the resulting DIE
+ *
+ * This function follows the pointer in @ptr_die with given @offset
+ * and saves the resulting type in @die_mem. If the pointer points
+ * a struct type, actual member at the offset would be returned.
+ */
+Dwarf_Die *die_deref_ptr_type(Dwarf_Die *ptr_die, int offset,
+ Dwarf_Die *die_mem)
+{
+ Dwarf_Die type_die;
+
+ if (dwarf_tag(ptr_die) != DW_TAG_pointer_type)
+ return NULL;
+
+ if (die_get_real_type(ptr_die, &type_die) == NULL)
+ return NULL;
+
+ return die_get_member_type(&type_die, offset, die_mem);
+}