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.c187
1 files changed, 163 insertions, 24 deletions
diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 7aa5fee0da19..2791126069b4 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -1272,11 +1272,67 @@ struct find_var_data {
unsigned reg;
/* Access offset, set for global data */
int offset;
+ /* True if the current register is the frame base */
+ bool is_fbreg;
};
/* Max number of registers DW_OP_regN supports */
#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)
+{
+ Dwarf_Die type_die;
+ Dwarf_Word size;
+
+ if (addr_offset == addr_type) {
+ /* Update offset relative to the start of the variable */
+ data->offset = 0;
+ return true;
+ }
+
+ if (die_get_real_type(die_mem, &type_die) == NULL)
+ return false;
+
+ if (dwarf_aggregate_size(&type_die, &size) < 0)
+ return false;
+
+ if (addr_offset >= addr_type + size)
+ return false;
+
+ /* Update offset relative to the start of the variable */
+ data->offset = addr_offset - addr_type;
+ 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)
{
@@ -1301,13 +1357,35 @@ static int __die_find_var_reg_cb(Dwarf_Die *die_mem, void *arg)
if (start > data->pc)
break;
+ /* 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))
+ return DIE_FIND_CB_END;
+
/* Only match with a simple case */
if (data->reg < DWARF_OP_DIRECT_REGS) {
- if (ops->atom == (DW_OP_reg0 + data->reg) && nops == 1)
+ /* pointer variables saved in a register 0 to 31 */
+ if (ops->atom == (DW_OP_reg0 + data->reg) &&
+ check_allowed_ops(ops, nops))
+ 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))
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 &&
- nops == 1)
+ check_allowed_ops(ops, nops))
+ 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))
return DIE_FIND_CB_END;
}
}
@@ -1319,18 +1397,29 @@ static int __die_find_var_reg_cb(Dwarf_Die *die_mem, void *arg)
* @sc_die: a scope DIE
* @pc: the program address to find
* @reg: the register number to find
+ * @poffset: pointer to offset, will be updated for fbreg case
+ * @is_fbreg: boolean value if the current register is the frame base
* @die_mem: a buffer to save the resulting DIE
*
- * Find the variable DIE accessed by the given register.
+ * Find the variable DIE accessed by the given register. It'll update the @offset
+ * when the variable is in the stack.
*/
Dwarf_Die *die_find_variable_by_reg(Dwarf_Die *sc_die, Dwarf_Addr pc, int reg,
+ int *poffset, bool is_fbreg,
Dwarf_Die *die_mem)
{
struct find_var_data data = {
.pc = pc,
.reg = reg,
+ .offset = *poffset,
+ .is_fbreg = is_fbreg,
};
- return die_find_child(sc_die, __die_find_var_reg_cb, &data, die_mem);
+ Dwarf_Die *result;
+
+ result = die_find_child(sc_die, __die_find_var_reg_cb, &data, die_mem);
+ if (result)
+ *poffset = data.offset;
+ return result;
}
/* Only checks direct child DIEs in the given scope */
@@ -1341,8 +1430,6 @@ static int __die_find_var_addr_cb(Dwarf_Die *die_mem, void *arg)
ptrdiff_t off = 0;
Dwarf_Attribute attr;
Dwarf_Addr base, start, end;
- Dwarf_Word size;
- Dwarf_Die type_die;
Dwarf_Op *ops;
size_t nops;
@@ -1359,24 +1446,9 @@ static int __die_find_var_addr_cb(Dwarf_Die *die_mem, void *arg)
if (data->addr < ops->number)
continue;
- if (data->addr == ops->number) {
- /* Update offset relative to the start of the variable */
- data->offset = 0;
+ if (check_allowed_ops(ops, nops) &&
+ match_var_offset(die_mem, data, data->addr, ops->number))
return DIE_FIND_CB_END;
- }
-
- if (die_get_real_type(die_mem, &type_die) == NULL)
- continue;
-
- if (dwarf_aggregate_size(&type_die, &size) < 0)
- continue;
-
- if (data->addr >= ops->number + size)
- continue;
-
- /* Update offset relative to the start of the variable */
- data->offset = data->addr - ops->number;
- return DIE_FIND_CB_END;
}
return DIE_FIND_CB_SIBLING;
}
@@ -1407,7 +1479,74 @@ Dwarf_Die *die_find_variable_by_addr(Dwarf_Die *sc_die, Dwarf_Addr pc,
*offset = data.offset;
return result;
}
-#endif
+#endif /* HAVE_DWARF_GETLOCATIONS_SUPPORT */
+
+#ifdef 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;
+ 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:
+ return op->number;
+ case DW_OP_bregx:
+ return op->number2;
+ default:
+ break;
+ }
+ return -1;
+}
+
+/**
+ * die_get_cfa - Get frame base information
+ * @dwarf: a Dwarf info
+ * @pc: program address
+ * @preg: pointer for saved register
+ * @poffset: pointer for saved offset
+ *
+ * This function gets register and offset for CFA (Canonical Frame Address)
+ * by searching the CIE/FDE info. The CFA usually points to the start address
+ * of the current stack frame and local variables can be located using an offset
+ * from the CFA. The @preg and @poffset will be updated if it returns 0.
+ */
+int die_get_cfa(Dwarf *dwarf, u64 pc, int *preg, int *poffset)
+{
+ Dwarf_CFI *cfi;
+ Dwarf_Frame *frame = NULL;
+ Dwarf_Op *ops = NULL;
+ size_t nops;
+
+ cfi = dwarf_getcfi(dwarf);
+ if (cfi == NULL)
+ return -1;
+
+ if (!dwarf_cfi_addrframe(cfi, pc, &frame) &&
+ !dwarf_frame_cfa(frame, &ops, &nops) &&
+ check_allowed_ops(ops, nops)) {
+ *preg = reg_from_dwarf_op(ops);
+ *poffset = offset_from_dwarf_op(ops);
+ return 0;
+ }
+ return -1;
+}
+#endif /* HAVE_DWARF_CFI_SUPPORT */
/*
* die_has_loclist - Check if DW_AT_location of @vr_die is a location list