Skip to content

Commit 9710b2f

Browse files
Kalesh Singhrostedt
authored andcommitted
tracing: Fix operator precedence for hist triggers expression
The current histogram expression evaluation logic evaluates the expression from right to left. This can lead to incorrect results if the operations are not associative (as is the case for subtraction and, the now added, division operators). e.g. 16-8-4-2 should be 2 not 10 --> 16-8-4-2 = ((16-8)-4)-2 64/8/4/2 should be 1 not 16 --> 64/8/4/2 = ((64/8)/4)/2 Division and multiplication are currently limited to single operation expression due to operator precedence support not yet implemented. Rework the expression parsing to support the correct evaluation of expressions containing operators of different precedences; and fix the associativity error by evaluating expressions with operators of the same precedence from left to right. Examples: (1) echo 'hist:keys=common_pid:a=8,b=4,c=2,d=1,w=$a-$b-$c-$d' \ >> event/trigger (2) echo 'hist:keys=common_pid:x=$a/$b/3/2' >> event/trigger (3) echo 'hist:keys=common_pid:y=$a+10/$c*1024' >> event/trigger (4) echo 'hist:keys=common_pid:z=$a/$b+$c*$d' >> event/trigger Link: https://lkml.kernel.org/r/[email protected] Signed-off-by: Kalesh Singh <[email protected]> Reviewed-by: Namhyung Kim <[email protected]> Signed-off-by: Steven Rostedt (VMware) <[email protected]>
1 parent bcef044 commit 9710b2f

File tree

1 file changed

+140
-70
lines changed

1 file changed

+140
-70
lines changed

kernel/trace/trace_events_hist.c

Lines changed: 140 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@
6767
C(TOO_MANY_SORT_FIELDS, "Too many sort fields (Max = 2)"), \
6868
C(INVALID_SORT_FIELD, "Sort field must be a key or a val"), \
6969
C(INVALID_STR_OPERAND, "String type can not be an operand in expression"), \
70-
C(EXPECT_NUMBER, "Expecting numeric literal"),
70+
C(EXPECT_NUMBER, "Expecting numeric literal"), \
71+
C(UNARY_MINUS_SUBEXPR, "Unary minus not supported in sub-expressions"), \
72+
C(SYM_OFFSET_SUBEXPR, ".sym-offset not supported in sub-expressions"),
7173

7274
#undef C
7375
#define C(a, b) HIST_ERR_##a
@@ -1644,40 +1646,96 @@ static char *expr_str(struct hist_field *field, unsigned int level)
16441646
return expr;
16451647
}
16461648

1647-
static int contains_operator(char *str)
1649+
/*
1650+
* If field_op != FIELD_OP_NONE, *sep points to the root operator
1651+
* of the expression tree to be evaluated.
1652+
*/
1653+
static int contains_operator(char *str, char **sep)
16481654
{
16491655
enum field_op_id field_op = FIELD_OP_NONE;
1650-
char *op;
1656+
char *minus_op, *plus_op, *div_op, *mult_op;
1657+
1658+
1659+
/*
1660+
* Report the last occurrence of the operators first, so that the
1661+
* expression is evaluated left to right. This is important since
1662+
* subtraction and division are not associative.
1663+
*
1664+
* e.g
1665+
* 64/8/4/2 is 1, i.e 64/8/4/2 = ((64/8)/4)/2
1666+
* 14-7-5-2 is 0, i.e 14-7-5-2 = ((14-7)-5)-2
1667+
*/
16511668

1652-
op = strpbrk(str, "+-/*");
1653-
if (!op)
1654-
return FIELD_OP_NONE;
1669+
/*
1670+
* First, find lower precedence addition and subtraction
1671+
* since the expression will be evaluated recursively.
1672+
*/
1673+
minus_op = strrchr(str, '-');
1674+
if (minus_op) {
1675+
/* Unfortunately, the modifier ".sym-offset" can confuse things. */
1676+
if (minus_op - str >= 4 && !strncmp(minus_op - 4, ".sym-offset", 11))
1677+
goto out;
16551678

1656-
switch (*op) {
1657-
case '-':
16581679
/*
1659-
* Unfortunately, the modifier ".sym-offset"
1660-
* can confuse things.
1680+
* Unary minus is not supported in sub-expressions. If
1681+
* present, it is always the next root operator.
16611682
*/
1662-
if (op - str >= 4 && !strncmp(op - 4, ".sym-offset", 11))
1663-
return FIELD_OP_NONE;
1664-
1665-
if (*str == '-')
1683+
if (minus_op == str) {
16661684
field_op = FIELD_OP_UNARY_MINUS;
1667-
else
1668-
field_op = FIELD_OP_MINUS;
1669-
break;
1670-
case '+':
1671-
field_op = FIELD_OP_PLUS;
1672-
break;
1673-
case '/':
1685+
goto out;
1686+
}
1687+
1688+
field_op = FIELD_OP_MINUS;
1689+
}
1690+
1691+
plus_op = strrchr(str, '+');
1692+
if (plus_op || minus_op) {
1693+
/*
1694+
* For operators of the same precedence use to rightmost as the
1695+
* root, so that the expression is evaluated left to right.
1696+
*/
1697+
if (plus_op > minus_op)
1698+
field_op = FIELD_OP_PLUS;
1699+
goto out;
1700+
}
1701+
1702+
/*
1703+
* Multiplication and division have higher precedence than addition and
1704+
* subtraction.
1705+
*/
1706+
div_op = strrchr(str, '/');
1707+
if (div_op)
16741708
field_op = FIELD_OP_DIV;
1675-
break;
1676-
case '*':
1709+
1710+
mult_op = strrchr(str, '*');
1711+
/*
1712+
* For operators of the same precedence use to rightmost as the
1713+
* root, so that the expression is evaluated left to right.
1714+
*/
1715+
if (mult_op > div_op)
16771716
field_op = FIELD_OP_MULT;
1678-
break;
1679-
default:
1680-
break;
1717+
1718+
out:
1719+
if (sep) {
1720+
switch (field_op) {
1721+
case FIELD_OP_UNARY_MINUS:
1722+
case FIELD_OP_MINUS:
1723+
*sep = minus_op;
1724+
break;
1725+
case FIELD_OP_PLUS:
1726+
*sep = plus_op;
1727+
break;
1728+
case FIELD_OP_DIV:
1729+
*sep = div_op;
1730+
break;
1731+
case FIELD_OP_MULT:
1732+
*sep = mult_op;
1733+
break;
1734+
case FIELD_OP_NONE:
1735+
default:
1736+
*sep = NULL;
1737+
break;
1738+
}
16811739
}
16821740

16831741
return field_op;
@@ -2003,7 +2061,7 @@ static char *field_name_from_var(struct hist_trigger_data *hist_data,
20032061

20042062
if (strcmp(var_name, name) == 0) {
20052063
field = hist_data->attrs->var_defs.expr[i];
2006-
if (contains_operator(field) || is_var_ref(field))
2064+
if (contains_operator(field, NULL) || is_var_ref(field))
20072065
continue;
20082066
return field;
20092067
}
@@ -2266,21 +2324,24 @@ static struct hist_field *parse_atom(struct hist_trigger_data *hist_data,
22662324
static struct hist_field *parse_expr(struct hist_trigger_data *hist_data,
22672325
struct trace_event_file *file,
22682326
char *str, unsigned long flags,
2269-
char *var_name, unsigned int level);
2327+
char *var_name, unsigned int *n_subexprs);
22702328

22712329
static struct hist_field *parse_unary(struct hist_trigger_data *hist_data,
22722330
struct trace_event_file *file,
22732331
char *str, unsigned long flags,
2274-
char *var_name, unsigned int level)
2332+
char *var_name, unsigned int *n_subexprs)
22752333
{
22762334
struct hist_field *operand1, *expr = NULL;
22772335
unsigned long operand_flags;
22782336
int ret = 0;
22792337
char *s;
22802338

2339+
/* Unary minus operator, increment n_subexprs */
2340+
++*n_subexprs;
2341+
22812342
/* we support only -(xxx) i.e. explicit parens required */
22822343

2283-
if (level > 3) {
2344+
if (*n_subexprs > 3) {
22842345
hist_err(file->tr, HIST_ERR_TOO_MANY_SUBEXPR, errpos(str));
22852346
ret = -EINVAL;
22862347
goto free;
@@ -2297,8 +2358,16 @@ static struct hist_field *parse_unary(struct hist_trigger_data *hist_data,
22972358
}
22982359

22992360
s = strrchr(str, ')');
2300-
if (s)
2361+
if (s) {
2362+
/* unary minus not supported in sub-expressions */
2363+
if (*(s+1) != '\0') {
2364+
hist_err(file->tr, HIST_ERR_UNARY_MINUS_SUBEXPR,
2365+
errpos(str));
2366+
ret = -EINVAL;
2367+
goto free;
2368+
}
23012369
*s = '\0';
2370+
}
23022371
else {
23032372
ret = -EINVAL; /* no closing ')' */
23042373
goto free;
@@ -2312,7 +2381,7 @@ static struct hist_field *parse_unary(struct hist_trigger_data *hist_data,
23122381
}
23132382

23142383
operand_flags = 0;
2315-
operand1 = parse_expr(hist_data, file, str, operand_flags, NULL, ++level);
2384+
operand1 = parse_expr(hist_data, file, str, operand_flags, NULL, n_subexprs);
23162385
if (IS_ERR(operand1)) {
23172386
ret = PTR_ERR(operand1);
23182387
goto free;
@@ -2382,60 +2451,61 @@ static int check_expr_operands(struct trace_array *tr,
23822451
static struct hist_field *parse_expr(struct hist_trigger_data *hist_data,
23832452
struct trace_event_file *file,
23842453
char *str, unsigned long flags,
2385-
char *var_name, unsigned int level)
2454+
char *var_name, unsigned int *n_subexprs)
23862455
{
23872456
struct hist_field *operand1 = NULL, *operand2 = NULL, *expr = NULL;
23882457
unsigned long operand_flags;
23892458
int field_op, ret = -EINVAL;
23902459
char *sep, *operand1_str;
23912460

2392-
if (level > 3) {
2461+
if (*n_subexprs > 3) {
23932462
hist_err(file->tr, HIST_ERR_TOO_MANY_SUBEXPR, errpos(str));
23942463
return ERR_PTR(-EINVAL);
23952464
}
23962465

2397-
field_op = contains_operator(str);
2466+
/*
2467+
* ".sym-offset" in expressions has no effect on their evaluation,
2468+
* but can confuse operator parsing.
2469+
*/
2470+
if (*n_subexprs == 0) {
2471+
sep = strstr(str, ".sym-offset");
2472+
if (sep) {
2473+
*sep = '\0';
2474+
if (strpbrk(str, "+-/*") || strpbrk(sep + 11, "+-/*")) {
2475+
*sep = '.';
2476+
hist_err(file->tr, HIST_ERR_SYM_OFFSET_SUBEXPR,
2477+
errpos(sep));
2478+
return ERR_PTR(-EINVAL);
2479+
}
2480+
*sep = '.';
2481+
}
2482+
}
2483+
2484+
field_op = contains_operator(str, &sep);
23982485

23992486
if (field_op == FIELD_OP_NONE)
24002487
return parse_atom(hist_data, file, str, &flags, var_name);
24012488

24022489
if (field_op == FIELD_OP_UNARY_MINUS)
2403-
return parse_unary(hist_data, file, str, flags, var_name, ++level);
2490+
return parse_unary(hist_data, file, str, flags, var_name, n_subexprs);
24042491

2405-
switch (field_op) {
2406-
case FIELD_OP_MINUS:
2407-
sep = "-";
2408-
break;
2409-
case FIELD_OP_PLUS:
2410-
sep = "+";
2411-
break;
2412-
case FIELD_OP_DIV:
2413-
sep = "/";
2414-
break;
2415-
case FIELD_OP_MULT:
2416-
sep = "*";
2417-
break;
2418-
default:
2419-
goto free;
2420-
}
2492+
/* Binary operator found, increment n_subexprs */
2493+
++*n_subexprs;
24212494

2422-
/*
2423-
* Multiplication and division are only supported in single operator
2424-
* expressions, since the expression is always evaluated from right
2425-
* to left.
2426-
*/
2427-
if ((field_op == FIELD_OP_DIV || field_op == FIELD_OP_MULT) && level > 0) {
2428-
hist_err(file->tr, HIST_ERR_TOO_MANY_SUBEXPR, errpos(str));
2429-
return ERR_PTR(-EINVAL);
2430-
}
2495+
/* Split the expression string at the root operator */
2496+
if (!sep)
2497+
goto free;
2498+
*sep = '\0';
2499+
operand1_str = str;
2500+
str = sep+1;
24312501

2432-
operand1_str = strsep(&str, sep);
24332502
if (!operand1_str || !str)
24342503
goto free;
24352504

24362505
operand_flags = 0;
2437-
operand1 = parse_atom(hist_data, file, operand1_str,
2438-
&operand_flags, NULL);
2506+
2507+
/* LHS of string is an expression e.g. a+b in a+b+c */
2508+
operand1 = parse_expr(hist_data, file, operand1_str, operand_flags, NULL, n_subexprs);
24392509
if (IS_ERR(operand1)) {
24402510
ret = PTR_ERR(operand1);
24412511
operand1 = NULL;
@@ -2447,9 +2517,9 @@ static struct hist_field *parse_expr(struct hist_trigger_data *hist_data,
24472517
goto free;
24482518
}
24492519

2450-
/* rest of string could be another expression e.g. b+c in a+b+c */
2520+
/* RHS of string is another expression e.g. c in a+b+c */
24512521
operand_flags = 0;
2452-
operand2 = parse_expr(hist_data, file, str, operand_flags, NULL, ++level);
2522+
operand2 = parse_expr(hist_data, file, str, operand_flags, NULL, n_subexprs);
24532523
if (IS_ERR(operand2)) {
24542524
ret = PTR_ERR(operand2);
24552525
operand2 = NULL;
@@ -3883,9 +3953,9 @@ static int __create_val_field(struct hist_trigger_data *hist_data,
38833953
unsigned long flags)
38843954
{
38853955
struct hist_field *hist_field;
3886-
int ret = 0;
3956+
int ret = 0, n_subexprs = 0;
38873957

3888-
hist_field = parse_expr(hist_data, file, field_str, flags, var_name, 0);
3958+
hist_field = parse_expr(hist_data, file, field_str, flags, var_name, &n_subexprs);
38893959
if (IS_ERR(hist_field)) {
38903960
ret = PTR_ERR(hist_field);
38913961
goto out;
@@ -4026,7 +4096,7 @@ static int create_key_field(struct hist_trigger_data *hist_data,
40264096
struct hist_field *hist_field = NULL;
40274097
unsigned long flags = 0;
40284098
unsigned int key_size;
4029-
int ret = 0;
4099+
int ret = 0, n_subexprs = 0;
40304100

40314101
if (WARN_ON(key_idx >= HIST_FIELDS_MAX))
40324102
return -EINVAL;
@@ -4039,7 +4109,7 @@ static int create_key_field(struct hist_trigger_data *hist_data,
40394109
hist_field = create_hist_field(hist_data, NULL, flags, NULL);
40404110
} else {
40414111
hist_field = parse_expr(hist_data, file, field_str, flags,
4042-
NULL, 0);
4112+
NULL, &n_subexprs);
40434113
if (IS_ERR(hist_field)) {
40444114
ret = PTR_ERR(hist_field);
40454115
goto out;

0 commit comments

Comments
 (0)