Skip to content

Commit c61b871

Browse files
tracing: Fix operator precedence for hist triggers expression
JIRA: https://issues.redhat.com/browse/RHEL-67679 commit 9710b2f Author: Kalesh Singh <kaleshsingh@google.com> Date: Mon Oct 25 13:08:35 2021 -0700 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/20211025200852.3002369-4-kaleshsingh@google.com Signed-off-by: Kalesh Singh <kaleshsingh@google.com> Reviewed-by: Namhyung Kim <namhyung@kernel.org> Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org> Signed-off-by: Tomas Glozar <tglozar@redhat.com>
1 parent 41bc755 commit c61b871

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
@@ -1634,40 +1636,96 @@ static char *expr_str(struct hist_field *field, unsigned int level)
16341636
return expr;
16351637
}
16361638

1637-
static int contains_operator(char *str)
1639+
/*
1640+
* If field_op != FIELD_OP_NONE, *sep points to the root operator
1641+
* of the expression tree to be evaluated.
1642+
*/
1643+
static int contains_operator(char *str, char **sep)
16381644
{
16391645
enum field_op_id field_op = FIELD_OP_NONE;
1640-
char *op;
1646+
char *minus_op, *plus_op, *div_op, *mult_op;
1647+
1648+
1649+
/*
1650+
* Report the last occurrence of the operators first, so that the
1651+
* expression is evaluated left to right. This is important since
1652+
* subtraction and division are not associative.
1653+
*
1654+
* e.g
1655+
* 64/8/4/2 is 1, i.e 64/8/4/2 = ((64/8)/4)/2
1656+
* 14-7-5-2 is 0, i.e 14-7-5-2 = ((14-7)-5)-2
1657+
*/
16411658

1642-
op = strpbrk(str, "+-/*");
1643-
if (!op)
1644-
return FIELD_OP_NONE;
1659+
/*
1660+
* First, find lower precedence addition and subtraction
1661+
* since the expression will be evaluated recursively.
1662+
*/
1663+
minus_op = strrchr(str, '-');
1664+
if (minus_op) {
1665+
/* Unfortunately, the modifier ".sym-offset" can confuse things. */
1666+
if (minus_op - str >= 4 && !strncmp(minus_op - 4, ".sym-offset", 11))
1667+
goto out;
16451668

1646-
switch (*op) {
1647-
case '-':
16481669
/*
1649-
* Unfortunately, the modifier ".sym-offset"
1650-
* can confuse things.
1670+
* Unary minus is not supported in sub-expressions. If
1671+
* present, it is always the next root operator.
16511672
*/
1652-
if (op - str >= 4 && !strncmp(op - 4, ".sym-offset", 11))
1653-
return FIELD_OP_NONE;
1654-
1655-
if (*str == '-')
1673+
if (minus_op == str) {
16561674
field_op = FIELD_OP_UNARY_MINUS;
1657-
else
1658-
field_op = FIELD_OP_MINUS;
1659-
break;
1660-
case '+':
1661-
field_op = FIELD_OP_PLUS;
1662-
break;
1663-
case '/':
1675+
goto out;
1676+
}
1677+
1678+
field_op = FIELD_OP_MINUS;
1679+
}
1680+
1681+
plus_op = strrchr(str, '+');
1682+
if (plus_op || minus_op) {
1683+
/*
1684+
* For operators of the same precedence use to rightmost as the
1685+
* root, so that the expression is evaluated left to right.
1686+
*/
1687+
if (plus_op > minus_op)
1688+
field_op = FIELD_OP_PLUS;
1689+
goto out;
1690+
}
1691+
1692+
/*
1693+
* Multiplication and division have higher precedence than addition and
1694+
* subtraction.
1695+
*/
1696+
div_op = strrchr(str, '/');
1697+
if (div_op)
16641698
field_op = FIELD_OP_DIV;
1665-
break;
1666-
case '*':
1699+
1700+
mult_op = strrchr(str, '*');
1701+
/*
1702+
* For operators of the same precedence use to rightmost as the
1703+
* root, so that the expression is evaluated left to right.
1704+
*/
1705+
if (mult_op > div_op)
16671706
field_op = FIELD_OP_MULT;
1668-
break;
1669-
default:
1670-
break;
1707+
1708+
out:
1709+
if (sep) {
1710+
switch (field_op) {
1711+
case FIELD_OP_UNARY_MINUS:
1712+
case FIELD_OP_MINUS:
1713+
*sep = minus_op;
1714+
break;
1715+
case FIELD_OP_PLUS:
1716+
*sep = plus_op;
1717+
break;
1718+
case FIELD_OP_DIV:
1719+
*sep = div_op;
1720+
break;
1721+
case FIELD_OP_MULT:
1722+
*sep = mult_op;
1723+
break;
1724+
case FIELD_OP_NONE:
1725+
default:
1726+
*sep = NULL;
1727+
break;
1728+
}
16711729
}
16721730

16731731
return field_op;
@@ -1997,7 +2055,7 @@ static char *field_name_from_var(struct hist_trigger_data *hist_data,
19972055

19982056
if (strcmp(var_name, name) == 0) {
19992057
field = hist_data->attrs->var_defs.expr[i];
2000-
if (contains_operator(field) || is_var_ref(field))
2058+
if (contains_operator(field, NULL) || is_var_ref(field))
20012059
continue;
20022060
return field;
20032061
}
@@ -2260,21 +2318,24 @@ static struct hist_field *parse_atom(struct hist_trigger_data *hist_data,
22602318
static struct hist_field *parse_expr(struct hist_trigger_data *hist_data,
22612319
struct trace_event_file *file,
22622320
char *str, unsigned long flags,
2263-
char *var_name, unsigned int level);
2321+
char *var_name, unsigned int *n_subexprs);
22642322

22652323
static struct hist_field *parse_unary(struct hist_trigger_data *hist_data,
22662324
struct trace_event_file *file,
22672325
char *str, unsigned long flags,
2268-
char *var_name, unsigned int level)
2326+
char *var_name, unsigned int *n_subexprs)
22692327
{
22702328
struct hist_field *operand1, *expr = NULL;
22712329
unsigned long operand_flags;
22722330
int ret = 0;
22732331
char *s;
22742332

2333+
/* Unary minus operator, increment n_subexprs */
2334+
++*n_subexprs;
2335+
22752336
/* we support only -(xxx) i.e. explicit parens required */
22762337

2277-
if (level > 3) {
2338+
if (*n_subexprs > 3) {
22782339
hist_err(file->tr, HIST_ERR_TOO_MANY_SUBEXPR, errpos(str));
22792340
ret = -EINVAL;
22802341
goto free;
@@ -2291,8 +2352,16 @@ static struct hist_field *parse_unary(struct hist_trigger_data *hist_data,
22912352
}
22922353

22932354
s = strrchr(str, ')');
2294-
if (s)
2355+
if (s) {
2356+
/* unary minus not supported in sub-expressions */
2357+
if (*(s+1) != '\0') {
2358+
hist_err(file->tr, HIST_ERR_UNARY_MINUS_SUBEXPR,
2359+
errpos(str));
2360+
ret = -EINVAL;
2361+
goto free;
2362+
}
22952363
*s = '\0';
2364+
}
22962365
else {
22972366
ret = -EINVAL; /* no closing ')' */
22982367
goto free;
@@ -2306,7 +2375,7 @@ static struct hist_field *parse_unary(struct hist_trigger_data *hist_data,
23062375
}
23072376

23082377
operand_flags = 0;
2309-
operand1 = parse_expr(hist_data, file, str, operand_flags, NULL, ++level);
2378+
operand1 = parse_expr(hist_data, file, str, operand_flags, NULL, n_subexprs);
23102379
if (IS_ERR(operand1)) {
23112380
ret = PTR_ERR(operand1);
23122381
goto free;
@@ -2376,60 +2445,61 @@ static int check_expr_operands(struct trace_array *tr,
23762445
static struct hist_field *parse_expr(struct hist_trigger_data *hist_data,
23772446
struct trace_event_file *file,
23782447
char *str, unsigned long flags,
2379-
char *var_name, unsigned int level)
2448+
char *var_name, unsigned int *n_subexprs)
23802449
{
23812450
struct hist_field *operand1 = NULL, *operand2 = NULL, *expr = NULL;
23822451
unsigned long operand_flags;
23832452
int field_op, ret = -EINVAL;
23842453
char *sep, *operand1_str;
23852454

2386-
if (level > 3) {
2455+
if (*n_subexprs > 3) {
23872456
hist_err(file->tr, HIST_ERR_TOO_MANY_SUBEXPR, errpos(str));
23882457
return ERR_PTR(-EINVAL);
23892458
}
23902459

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

23932480
if (field_op == FIELD_OP_NONE)
23942481
return parse_atom(hist_data, file, str, &flags, var_name);
23952482

23962483
if (field_op == FIELD_OP_UNARY_MINUS)
2397-
return parse_unary(hist_data, file, str, flags, var_name, ++level);
2484+
return parse_unary(hist_data, file, str, flags, var_name, n_subexprs);
23982485

2399-
switch (field_op) {
2400-
case FIELD_OP_MINUS:
2401-
sep = "-";
2402-
break;
2403-
case FIELD_OP_PLUS:
2404-
sep = "+";
2405-
break;
2406-
case FIELD_OP_DIV:
2407-
sep = "/";
2408-
break;
2409-
case FIELD_OP_MULT:
2410-
sep = "*";
2411-
break;
2412-
default:
2413-
goto free;
2414-
}
2486+
/* Binary operator found, increment n_subexprs */
2487+
++*n_subexprs;
24152488

2416-
/*
2417-
* Multiplication and division are only supported in single operator
2418-
* expressions, since the expression is always evaluated from right
2419-
* to left.
2420-
*/
2421-
if ((field_op == FIELD_OP_DIV || field_op == FIELD_OP_MULT) && level > 0) {
2422-
hist_err(file->tr, HIST_ERR_TOO_MANY_SUBEXPR, errpos(str));
2423-
return ERR_PTR(-EINVAL);
2424-
}
2489+
/* Split the expression string at the root operator */
2490+
if (!sep)
2491+
goto free;
2492+
*sep = '\0';
2493+
operand1_str = str;
2494+
str = sep+1;
24252495

2426-
operand1_str = strsep(&str, sep);
24272496
if (!operand1_str || !str)
24282497
goto free;
24292498

24302499
operand_flags = 0;
2431-
operand1 = parse_atom(hist_data, file, operand1_str,
2432-
&operand_flags, NULL);
2500+
2501+
/* LHS of string is an expression e.g. a+b in a+b+c */
2502+
operand1 = parse_expr(hist_data, file, operand1_str, operand_flags, NULL, n_subexprs);
24332503
if (IS_ERR(operand1)) {
24342504
ret = PTR_ERR(operand1);
24352505
operand1 = NULL;
@@ -2441,9 +2511,9 @@ static struct hist_field *parse_expr(struct hist_trigger_data *hist_data,
24412511
goto free;
24422512
}
24432513

2444-
/* rest of string could be another expression e.g. b+c in a+b+c */
2514+
/* RHS of string is another expression e.g. c in a+b+c */
24452515
operand_flags = 0;
2446-
operand2 = parse_expr(hist_data, file, str, operand_flags, NULL, ++level);
2516+
operand2 = parse_expr(hist_data, file, str, operand_flags, NULL, n_subexprs);
24472517
if (IS_ERR(operand2)) {
24482518
ret = PTR_ERR(operand2);
24492519
operand2 = NULL;
@@ -3879,9 +3949,9 @@ static int __create_val_field(struct hist_trigger_data *hist_data,
38793949
unsigned long flags)
38803950
{
38813951
struct hist_field *hist_field;
3882-
int ret = 0;
3952+
int ret = 0, n_subexprs = 0;
38833953

3884-
hist_field = parse_expr(hist_data, file, field_str, flags, var_name, 0);
3954+
hist_field = parse_expr(hist_data, file, field_str, flags, var_name, &n_subexprs);
38853955
if (IS_ERR(hist_field)) {
38863956
ret = PTR_ERR(hist_field);
38873957
goto out;
@@ -3984,7 +4054,7 @@ static int create_key_field(struct hist_trigger_data *hist_data,
39844054
struct hist_field *hist_field = NULL;
39854055
unsigned long flags = 0;
39864056
unsigned int key_size;
3987-
int ret = 0;
4057+
int ret = 0, n_subexprs = 0;
39884058

39894059
if (WARN_ON(key_idx >= HIST_FIELDS_MAX))
39904060
return -EINVAL;
@@ -3997,7 +4067,7 @@ static int create_key_field(struct hist_trigger_data *hist_data,
39974067
hist_field = create_hist_field(hist_data, NULL, flags, NULL);
39984068
} else {
39994069
hist_field = parse_expr(hist_data, file, field_str, flags,
4000-
NULL, 0);
4070+
NULL, &n_subexprs);
40014071
if (IS_ERR(hist_field)) {
40024072
ret = PTR_ERR(hist_field);
40034073
goto out;

0 commit comments

Comments
 (0)