summaryrefslogtreecommitdiff
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.c442
1 files changed, 369 insertions, 73 deletions
diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 2791126069b4..44ef968a7ad3 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);
@@ -1136,6 +1182,71 @@ int die_get_varname(Dwarf_Die *vr_die, struct strbuf *buf)
return ret < 0 ? ret : strbuf_addf(buf, "\t%s", dwarf_diename(vr_die));
}
+#if defined(HAVE_DWARF_GETLOCATIONS_SUPPORT) || defined(HAVE_DWARF_CFI_SUPPORT)
+static int reg_from_dwarf_op(Dwarf_Op *op)
+{
+ switch (op->atom) {
+ case DW_OP_reg0 ... DW_OP_reg31:
+ return op->atom - DW_OP_reg0;
+ case DW_OP_breg0 ... DW_OP_breg31:
+ return op->atom - DW_OP_breg0;
+ case DW_OP_regx:
+ case DW_OP_bregx:
+ return op->number;
+ case DW_OP_fbreg:
+ return DWARF_REG_FB;
+ default:
+ break;
+ }
+ return -1;
+}
+
+static int offset_from_dwarf_op(Dwarf_Op *op)
+{
+ switch (op->atom) {
+ case DW_OP_reg0 ... DW_OP_reg31:
+ 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;
+ default:
+ break;
+ }
+ return -1;
+}
+
+static bool check_allowed_ops(Dwarf_Op *ops, size_t nops)
+{
+ /* The first op is checked separately */
+ ops++;
+ nops--;
+
+ /*
+ * It needs to make sure if the location expression matches to the given
+ * register and offset exactly. Thus it rejects any complex expressions
+ * and only allows a few of selected operators that doesn't change the
+ * location.
+ */
+ while (nops) {
+ switch (ops->atom) {
+ case DW_OP_stack_value:
+ case DW_OP_deref_size:
+ case DW_OP_deref:
+ case DW_OP_piece:
+ break;
+ default:
+ return false;
+ }
+ ops++;
+ nops--;
+ }
+ return true;
+}
+#endif /* HAVE_DWARF_GETLOCATIONS_SUPPORT || HAVE_DWARF_CFI_SUPPORT */
+
#ifdef HAVE_DWARF_GETLOCATIONS_SUPPORT
/**
* die_get_var_innermost_scope - Get innermost scope range of given variable DIE
@@ -1280,7 +1391,7 @@ struct find_var_data {
#define DWARF_OP_DIRECT_REGS 32
static bool match_var_offset(Dwarf_Die *die_mem, struct find_var_data *data,
- u64 addr_offset, u64 addr_type)
+ u64 addr_offset, u64 addr_type, bool is_pointer)
{
Dwarf_Die type_die;
Dwarf_Word size;
@@ -1291,9 +1402,18 @@ 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;
+ if (is_pointer && dwarf_tag(&type_die) == DW_TAG_pointer_type) {
+ /* Get the target type of the pointer */
+ if (die_get_real_type(&type_die, &type_die) == NULL)
+ return false;
+ }
+
if (dwarf_aggregate_size(&type_die, &size) < 0)
return false;
@@ -1305,34 +1425,6 @@ static bool match_var_offset(Dwarf_Die *die_mem, struct find_var_data *data,
return true;
}
-static bool check_allowed_ops(Dwarf_Op *ops, size_t nops)
-{
- /* The first op is checked separately */
- ops++;
- nops--;
-
- /*
- * It needs to make sure if the location expression matches to the given
- * register and offset exactly. Thus it rejects any complex expressions
- * and only allows a few of selected operators that doesn't change the
- * location.
- */
- while (nops) {
- switch (ops->atom) {
- case DW_OP_stack_value:
- case DW_OP_deref_size:
- case DW_OP_deref:
- case DW_OP_piece:
- break;
- default:
- return false;
- }
- ops++;
- nops--;
- }
- return true;
-}
-
/* Only checks direct child DIEs in the given scope. */
static int __die_find_var_reg_cb(Dwarf_Die *die_mem, void *arg)
{
@@ -1359,33 +1451,39 @@ 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))
+ match_var_offset(die_mem, data, data->offset, ops->number,
+ /*is_pointer=*/false))
return DIE_FIND_CB_END;
/* Only match with a simple case */
if (data->reg < DWARF_OP_DIRECT_REGS) {
/* pointer variables saved in a register 0 to 31 */
if (ops->atom == (DW_OP_reg0 + data->reg) &&
- check_allowed_ops(ops, nops))
+ check_allowed_ops(ops, nops) &&
+ match_var_offset(die_mem, data, data->offset, 0,
+ /*is_pointer=*/true))
return DIE_FIND_CB_END;
/* Local variables accessed by a register + offset */
if (ops->atom == (DW_OP_breg0 + data->reg) &&
check_allowed_ops(ops, nops) &&
- match_var_offset(die_mem, data, data->offset, ops->number))
+ match_var_offset(die_mem, data, data->offset, ops->number,
+ /*is_pointer=*/false))
return DIE_FIND_CB_END;
} else {
/* pointer variables saved in a register 32 or above */
if (ops->atom == DW_OP_regx && ops->number == data->reg &&
- check_allowed_ops(ops, nops))
+ check_allowed_ops(ops, nops) &&
+ match_var_offset(die_mem, data, data->offset, 0,
+ /*is_pointer=*/true))
return DIE_FIND_CB_END;
/* Local variables accessed by a register + offset */
if (ops->atom == DW_OP_bregx && data->reg == ops->number &&
check_allowed_ops(ops, nops) &&
- match_var_offset(die_mem, data, data->offset, ops->number2))
+ match_var_offset(die_mem, data, data->offset, ops->number2,
+ /*is_poitner=*/false))
return DIE_FIND_CB_END;
}
}
@@ -1443,11 +1541,9 @@ 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))
+ match_var_offset(die_mem, data, data->addr, ops->number,
+ /*is_pointer=*/false))
return DIE_FIND_CB_END;
}
return DIE_FIND_CB_SIBLING;
@@ -1456,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
@@ -1464,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;
@@ -1479,42 +1572,132 @@ Dwarf_Die *die_find_variable_by_addr(Dwarf_Die *sc_die, Dwarf_Addr pc,
*offset = data.offset;
return result;
}
-#endif /* HAVE_DWARF_GETLOCATIONS_SUPPORT */
-#ifdef HAVE_DWARF_CFI_SUPPORT
-static int reg_from_dwarf_op(Dwarf_Op *op)
+static int __die_collect_vars_cb(Dwarf_Die *die_mem, void *arg)
{
- switch (op->atom) {
- case DW_OP_reg0 ... DW_OP_reg31:
- return op->atom - DW_OP_reg0;
- case DW_OP_breg0 ... DW_OP_breg31:
- return op->atom - DW_OP_breg0;
- case DW_OP_regx:
- case DW_OP_bregx:
- return op->number;
- default:
- break;
- }
- return -1;
+ 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 && tag != DW_TAG_formal_parameter)
+ return DIE_FIND_CB_SIBLING;
+
+ if (dwarf_attr(die_mem, DW_AT_location, &attr) == NULL)
+ return DIE_FIND_CB_SIBLING;
+
+ /*
+ * Only collect the first location as it can reconstruct the
+ * remaining state by following the instructions.
+ * start = 0 means it covers the whole range.
+ */
+ if (dwarf_getlocations(&attr, 0, &base, &start, &end, &ops, &nops) <= 0)
+ 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 = start;
+ vt->reg = reg_from_dwarf_op(ops);
+ vt->offset = offset_from_dwarf_op(ops);
+ vt->next = *var_types;
+ *var_types = vt;
+
+ return DIE_FIND_CB_SIBLING;
}
-static int offset_from_dwarf_op(Dwarf_Op *op)
+/**
+ * die_collect_vars - Save all variables and parameters
+ * @sc_die: a scope DIE
+ * @var_types: a pointer to save the resulting list
+ *
+ * Save all variables and parameters in the @sc_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_vars(Dwarf_Die *sc_die, struct die_var_type **var_types)
{
- switch (op->atom) {
- case DW_OP_reg0 ... DW_OP_reg31:
- case DW_OP_regx:
- return 0;
- case DW_OP_breg0 ... DW_OP_breg31:
- return op->number;
- case DW_OP_bregx:
- return op->number2;
- default:
- break;
- }
- return -1;
+ Dwarf_Die die_mem;
+
+ 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
+/**
* die_get_cfa - Get frame base information
* @dwarf: a Dwarf info
* @pc: program address
@@ -1779,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);
+}