Commit 55442cc2 authored by Namhyung Kim's avatar Namhyung Kim

perf dwarf-aux: Check allowed DWARF Ops

The DWARF location expression can be fairly complex and it'd be hard
to match it with the condition correctly.  So let's be conservative
and only allow simple expressions.  For now it just checks the first
operation in the list.  The following operations looks ok:

 * DW_OP_stack_value
 * DW_OP_deref_size
 * DW_OP_deref
 * DW_OP_piece

To refuse complex (and unsupported) location expressions, add
check_allowed_ops() to compare the rest of the list.  It seems earlier
result contained those unsupported expressions.  For example, I found
some local struct variable is placed like below.

 <2><43d1517>: Abbrev Number: 62 (DW_TAG_variable)
    <43d1518>   DW_AT_location    : 15 byte block: 91 50 93 8 91 78 93 4 93 84 8 91 68 93 4
        (DW_OP_fbreg: -48; DW_OP_piece: 8;
         DW_OP_fbreg: -8; DW_OP_piece: 4;
         DW_OP_piece: 1028;
         DW_OP_fbreg: -24; DW_OP_piece: 4)

Another example is something like this.

    0057c8be ffffffffffffffff ffffffff812109f0 (base address)
    0057c8ce ffffffff812112b5 ffffffff812112c8 (DW_OP_breg3 (rbx): 0;
                                                DW_OP_constu: 18446744073709551612;
                                                DW_OP_and;
                                                DW_OP_stack_value)

It should refuse them.  After the change, the stat shows:

  Annotate data type stats:
  total 294, ok 158 (53.7%), bad 136 (46.3%)
  -----------------------------------------------------------
          30 : no_sym
          32 : no_mem_ops
          53 : no_var
          14 : no_typeinfo
           7 : bad_offset
Acked-by: default avatarMasami Hiramatsu (Google) <mhiramat@kernel.org>
Reviewed-by: default avatarIan Rogers <irogers@google.com>
Cc: Stephane Eranian <eranian@google.com>
Link: https://lore.kernel.org/r/20240117062657.985479-10-namhyung@kernel.orgSigned-off-by: default avatarNamhyung Kim <namhyung@kernel.org>
parent bc10db8e
...@@ -1305,6 +1305,34 @@ static bool match_var_offset(Dwarf_Die *die_mem, struct find_var_data *data, ...@@ -1305,6 +1305,34 @@ static bool match_var_offset(Dwarf_Die *die_mem, struct find_var_data *data,
return true; 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. */ /* Only checks direct child DIEs in the given scope. */
static int __die_find_var_reg_cb(Dwarf_Die *die_mem, void *arg) static int __die_find_var_reg_cb(Dwarf_Die *die_mem, void *arg)
{ {
...@@ -1332,25 +1360,31 @@ static int __die_find_var_reg_cb(Dwarf_Die *die_mem, void *arg) ...@@ -1332,25 +1360,31 @@ static int __die_find_var_reg_cb(Dwarf_Die *die_mem, void *arg)
/* Local variables accessed using frame base register */ /* Local variables accessed using frame base register */
if (data->is_fbreg && ops->atom == DW_OP_fbreg && if (data->is_fbreg && ops->atom == DW_OP_fbreg &&
data->offset >= (int)ops->number && 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))
return DIE_FIND_CB_END; return DIE_FIND_CB_END;
/* Only match with a simple case */ /* Only match with a simple case */
if (data->reg < DWARF_OP_DIRECT_REGS) { 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; return DIE_FIND_CB_END;
/* Local variables accessed by a register + offset */ /* Local variables accessed by a register + offset */
if (ops->atom == (DW_OP_breg0 + data->reg) && 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))
return DIE_FIND_CB_END; return DIE_FIND_CB_END;
} else { } else {
/* pointer variables saved in a register 32 or above */
if (ops->atom == DW_OP_regx && ops->number == data->reg && if (ops->atom == DW_OP_regx && ops->number == data->reg &&
nops == 1) check_allowed_ops(ops, nops))
return DIE_FIND_CB_END; return DIE_FIND_CB_END;
/* Local variables accessed by a register + offset */ /* Local variables accessed by a register + offset */
if (ops->atom == DW_OP_bregx && data->reg == ops->number && 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))
return DIE_FIND_CB_END; return DIE_FIND_CB_END;
} }
...@@ -1412,7 +1446,8 @@ static int __die_find_var_addr_cb(Dwarf_Die *die_mem, void *arg) ...@@ -1412,7 +1446,8 @@ static int __die_find_var_addr_cb(Dwarf_Die *die_mem, void *arg)
if (data->addr < ops->number) if (data->addr < ops->number)
continue; continue;
if (match_var_offset(die_mem, data, data->addr, ops->number)) if (check_allowed_ops(ops, nops) &&
match_var_offset(die_mem, data, data->addr, ops->number))
return DIE_FIND_CB_END; return DIE_FIND_CB_END;
} }
return DIE_FIND_CB_SIBLING; return DIE_FIND_CB_SIBLING;
...@@ -1503,7 +1538,8 @@ int die_get_cfa(Dwarf *dwarf, u64 pc, int *preg, int *poffset) ...@@ -1503,7 +1538,8 @@ int die_get_cfa(Dwarf *dwarf, u64 pc, int *preg, int *poffset)
return -1; return -1;
if (!dwarf_cfi_addrframe(cfi, pc, &frame) && if (!dwarf_cfi_addrframe(cfi, pc, &frame) &&
!dwarf_frame_cfa(frame, &ops, &nops) && nops == 1) { !dwarf_frame_cfa(frame, &ops, &nops) &&
check_allowed_ops(ops, nops)) {
*preg = reg_from_dwarf_op(ops); *preg = reg_from_dwarf_op(ops);
*poffset = offset_from_dwarf_op(ops); *poffset = offset_from_dwarf_op(ops);
return 0; return 0;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment