Add a field specifier option for the typecast. This works like container_of() macro. (STRUCT[,FIELD[.FIELD2...]])VAR This is equivalent to : container_of(VAR, struct STRUCT, FIELD[.FIELD2...]) For example: echo "f tick_nohz_handler next_tick=(tick_sched,sched_timer)timer->next_tick" >> dynamic_events This will trace tick_nohz_handler() with its tick_sched::next_tick which is converted from @timer by contianer_of(tick, struct tick_sched, sched_timer). So, if you enabkle both fprobes:tick_nohz_handler__entry and timer:hrtimer_expire_entry events, we will see something like: <idle>-0 [002] d.h1. 3778.087272: hrtimer_expire_entry: hrtimer=00000000d63db328 f unction=tick_nohz_handler now=3777450051040 <idle>-0 [002] d.h1. 3778.087281: tick_nohz_handler__entry: (tick_nohz_handler+0x4 /0x140) next_tick=3777450000000 Link: https://lore.kernel.org/all/178271365745.1176915.725923927180862257.stgit@devnote2/
Showing the changes to kernel (3 of 6 files). Show all
Unified · Split
| @@ -4322,8 +4322,8 @@ static const char readme_msg[] = · 4321 unmodified lines | ||
| 4322 | 4322 | #ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API |
| 4323 | 4323 | "\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n" |
| 4324 | 4324 | #ifdef CONFIG_PROBE_EVENTS_BTF_ARGS |
| 4325 | − "\t [(structname)]<argname>[->field[->field|.field...]],\n" | |
| 4326 | − "\t [(structname)](fetcharg)->field[->field|.field...],\n" | |
| 4325 | + "\t [(structname[,field])]<argname>[->field[->field|.field...]],\n" | |
| 4326 | + "\t [(structname[,field])](fetcharg)->field[->field|.field...],\n" | |
| 4327 | 4327 | #endif |
| 4328 | 4328 | #else |
| 4329 | 4329 | "\t $stack<index>, $stack, $retval, $comm,\n" |
| @@ -568,6 +568,64 @@ static int split_next_field(char *varname, char **next_field, · 567 unmodified lines | ||
| 568 | 568 | return ret; |
| 569 | 569 | } |
| 570 | 570 | |
| 571 | +/* Inner loop for solving dot operator ('.'). Return bit-offset of the given field */ | |
| 572 | +static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **ptype, | |
| 573 | + struct traceprobe_parse_context *ctx) | |
| 574 | +{ | |
| 575 | + const struct btf_type *type = *ptype; | |
| 576 | + const struct btf_member *field; | |
| 577 | + struct btf *btf = ctx_btf(ctx); | |
| 578 | + char *fieldname = *pfieldname; | |
| 579 | + int bitoffs = 0; | |
| 580 | + u32 anon_offs; | |
| 581 | + char *next; | |
| 582 | + int is_ptr; | |
| 583 | + | |
| 584 | + do { | |
| 585 | + next = NULL; | |
| 586 | + is_ptr = split_next_field(fieldname, &next, ctx); | |
| 587 | + if (is_ptr < 0) | |
| 588 | + return is_ptr; | |
| 589 | + | |
| 590 | + anon_offs = 0; | |
| 591 | + field = btf_find_struct_member(btf, type, fieldname, | |
| 592 | + &anon_offs); | |
| 593 | + if (IS_ERR(field)) { | |
| 594 | + trace_probe_log_err(ctx->offset, BAD_BTF_TID); | |
| 595 | + return PTR_ERR(field); | |
| 596 | + } | |
| 597 | + if (!field) { | |
| 598 | + trace_probe_log_err(ctx->offset, NO_BTF_FIELD); | |
| 599 | + return -ENOENT; | |
| 600 | + } | |
| 601 | + /* Add anonymous structure/union offset */ | |
| 602 | + bitoffs += anon_offs; | |
| 603 | + | |
| 604 | + /* Accumulate the bit-offsets of the dot-connected fields */ | |
| 605 | + if (btf_type_kflag(type)) { | |
| 606 | + bitoffs += BTF_MEMBER_BIT_OFFSET(field->offset); | |
| 607 | + ctx->last_bitsize = BTF_MEMBER_BITFIELD_SIZE(field->offset); | |
| 608 | + } else { | |
| 609 | + bitoffs += field->offset; | |
| 610 | + ctx->last_bitsize = 0; | |
| 611 | + } | |
| 612 | + | |
| 613 | + type = btf_type_skip_modifiers(btf, field->type, NULL); | |
| 614 | + if (!type) { | |
| 615 | + trace_probe_log_err(ctx->offset, BAD_BTF_TID); | |
| 616 | + return -EINVAL; | |
| 617 | + } | |
| 618 | + | |
| 619 | + if (next) | |
| 620 | + ctx->offset += next - fieldname; | |
| 621 | + fieldname = next; | |
| 622 | + } while (!is_ptr && fieldname); | |
| 623 | + | |
| 624 | + *pfieldname = fieldname; | |
| 625 | + *ptype = type; | |
| 626 | + | |
| 627 | + return bitoffs; | |
| 628 | +} | |
| 571 | 629 | /* |
| 572 | 630 | * Parse the field of data structure. The @type must be a pointer type |
| 573 | 631 | * pointing the target data structure type. |
| @@ -577,15 +635,13 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type, · 3 unmodified lines | ||
| 577 | 635 | struct traceprobe_parse_context *ctx) |
| 578 | 636 | { |
| 579 | 637 | struct fetch_insn *code = *pcode; |
| 580 | − const struct btf_member *field; | |
| 581 | − u32 bitoffs, anon_offs; | |
| 582 | − bool is_struct = ctx->struct_btf != NULL; | |
| 583 | 638 | struct btf *btf = ctx_btf(ctx); |
| 584 | − char *next; | |
| 585 | − int is_ptr; | |
| 639 | + bool is_first_field = true; | |
| 640 | + int bitoffs; | |
| 586 | 641 | |
| 587 | 642 | do { |
| 588 | − if (!is_struct) { | |
| 643 | + /* For the first field of typecast, @type will be the target structure type. */ | |
| 644 | + if (!(is_first_field && ctx->struct_btf)) { | |
| 589 | 645 | /* Outer loop for solving arrow operator ('->') */ |
| 590 | 646 | if (BTF_INFO_KIND(type->info) != BTF_KIND_PTR) { |
| 591 | 647 | trace_probe_log_err(ctx->offset, NO_PTR_STRCT); |
| @@ -599,60 +655,25 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type, · 7 unmodified lines | ||
| 599 | 655 | return -EINVAL; |
| 600 | 656 | } |
| 601 | 657 | } |
| 602 | − /* Only the first type can skip being a pointer */ | |
| 603 | − is_struct = false; | |
| 604 | − | |
| 605 | − bitoffs = 0; | |
| 606 | − do { | |
| 607 | − /* Inner loop for solving dot operator ('.') */ | |
| 608 | − next = NULL; | |
| 609 | − is_ptr = split_next_field(fieldname, &next, ctx); | |
| 610 | − if (is_ptr < 0) | |
| 611 | − return is_ptr; | |
| 612 | − | |
| 613 | − anon_offs = 0; | |
| 614 | − field = btf_find_struct_member(btf, type, fieldname, | |
| 615 | − &anon_offs); | |
| 616 | − if (IS_ERR(field)) { | |
| 617 | − trace_probe_log_err(ctx->offset, BAD_BTF_TID); | |
| 618 | − return PTR_ERR(field); | |
| 619 | − } | |
| 620 | − if (!field) { | |
| 621 | − trace_probe_log_err(ctx->offset, NO_BTF_FIELD); | |
| 622 | − return -ENOENT; | |
| 623 | − } | |
| 624 | − /* Add anonymous structure/union offset */ | |
| 625 | − bitoffs += anon_offs; | |
| 626 | − | |
| 627 | − /* Accumulate the bit-offsets of the dot-connected fields */ | |
| 628 | − if (btf_type_kflag(type)) { | |
| 629 | − bitoffs += BTF_MEMBER_BIT_OFFSET(field->offset); | |
| 630 | − ctx->last_bitsize = BTF_MEMBER_BITFIELD_SIZE(field->offset); | |
| 631 | − } else { | |
| 632 | − bitoffs += field->offset; | |
| 633 | − ctx->last_bitsize = 0; | |
| 634 | − } | |
| 635 | − | |
| 636 | − type = btf_type_skip_modifiers(btf, field->type, NULL); | |
| 637 | − if (!type) { | |
| 638 | − trace_probe_log_err(ctx->offset, BAD_BTF_TID); | |
| 639 | − return -EINVAL; | |
| 640 | − } | |
| 641 | − | |
| 642 | − ctx->offset += next - fieldname; | |
| 643 | − fieldname = next; | |
| 644 | − } while (!is_ptr && fieldname); | |
| 645 | 658 | |
| 659 | + bitoffs = get_bitoffset_of_field(&fieldname, &type, ctx); | |
| 660 | + if (bitoffs < 0) | |
| 661 | + return bitoffs; | |
| 646 | 662 | if (++code == end) { |
| 647 | 663 | trace_probe_log_err(ctx->offset, TOO_MANY_OPS); |
| 648 | 664 | return -EINVAL; |
| 649 | 665 | } |
| 650 | 666 | code->op = FETCH_OP_DEREF; /* TODO: user deref support */ |
| 651 | 667 | code->offset = bitoffs / 8; |
| 668 | + if (is_first_field && ctx->struct_btf) { | |
| 669 | + /* The first field can be typecasted with field option. */ | |
| 670 | + code->offset -= ctx->prefix_byteoffs; | |
| 671 | + } | |
| 652 | 672 | *pcode = code; |
| 653 | 673 | |
| 654 | 674 | ctx->last_bitoffs = bitoffs % 8; |
| 655 | 675 | ctx->last_type = type; |
| 676 | + is_first_field = false; | |
| 656 | 677 | } while (fieldname); |
| 657 | 678 | |
| 658 | 679 | return 0; |
| @@ -808,6 +829,46 @@ static int query_btf_struct(const char *sname, struct traceprobe_parse_context * · 149 unmodified lines | ||
| 808 | 829 | return 0; |
| 809 | 830 | } |
| 810 | 831 | |
| 832 | +static int parse_btf_casttype(char *casttype, struct traceprobe_parse_context *ctx) | |
| 833 | +{ | |
| 834 | + char *field; | |
| 835 | + int ret; | |
| 836 | + | |
| 837 | + /* Field option - evaluated later. */ | |
| 838 | + field = strchr(casttype, ','); | |
| 839 | + if (field) | |
| 840 | + *field++ = '\0'; | |
| 841 | + | |
| 842 | + ret = query_btf_struct(casttype, ctx); | |
| 843 | + if (ret < 0) { | |
| 844 | + trace_probe_log_err(ctx->offset, NO_PTR_STRCT); | |
| 845 | + return -EINVAL; | |
| 846 | + } | |
| 847 | + | |
| 848 | + if (field) { | |
| 849 | + struct btf_type *type = (struct btf_type *)ctx->last_struct; | |
| 850 | + | |
| 851 | + ctx->offset += field - casttype; | |
| 852 | + ret = get_bitoffset_of_field(&field, &ctx->last_struct, ctx); | |
| 853 | + if (ret < 0) | |
| 854 | + return ret; | |
| 855 | + if (ret % 8) { | |
| 856 | + trace_probe_log_err(ctx->offset, TYPECAST_NOT_ALIGNED); | |
| 857 | + return -EINVAL; | |
| 858 | + } | |
| 859 | + if (field != NULL) { | |
| 860 | + /* this means @field skips an arrow operator ("->"). */ | |
| 861 | + trace_probe_log_err(ctx->offset - 2, TYPECAST_BAD_ARROW); | |
| 862 | + return -EINVAL; | |
| 863 | + } | |
| 864 | + ctx->prefix_byteoffs = ret / 8; | |
| 865 | + /* Restore the original struct type (overwritten by get_bitoffset_of_field) */ | |
| 866 | + ctx->last_struct = type; | |
| 867 | + } | |
| 868 | + | |
| 869 | + return ret; | |
| 870 | +} | |
| 871 | + | |
| 811 | 872 | /* Find the matching closing parenthesis for a given opening parenthesis. */ |
| 812 | 873 | static char *find_matched_close_paren(char *s) |
| 813 | 874 | { |
| @@ -940,14 +1001,14 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode, · 126 unmodified lines | ||
| 940 | 1001 | tmp = close + 2; /* Skip ">" after inner variable name */ |
| 941 | 1002 | |
| 942 | 1003 | /* resolve the typecast struct name */ |
| 943 | − ret = query_btf_struct(arg + 1, ctx); | |
| 944 | − if (ret < 0) { | |
| 945 | − trace_probe_log_err(orig_offset + 1, NO_PTR_STRCT); | |
| 946 | − return -EINVAL; | |
| 947 | − } | |
| 1004 | + ctx->offset = orig_offset + 1; /* for the '(' */ | |
| 1005 | + ret = parse_btf_casttype(arg + 1, ctx); | |
| 1006 | + if (ret < 0) | |
| 1007 | + return ret; | |
| 948 | 1008 | |
| 949 | 1009 | ctx->offset = orig_offset + tmp - arg; |
| 950 | 1010 | ret = parse_btf_field(tmp, ctx->last_struct, pcode, end, ctx); |
| 1011 | + ctx->prefix_byteoffs = 0; | |
| 951 | 1012 | return ret; |
| 952 | 1013 | } |
| 953 | 1014 | |
| @@ -451,6 +451,7 @@ struct traceprobe_parse_context { · 450 unmodified lines | ||
| 451 | 451 | unsigned int flags; |
| 452 | 452 | int offset; |
| 453 | 453 | int nested_level; |
| 454 | + int prefix_byteoffs; /* The byte offset of the prefix field of typecast */ | |
| 454 | 455 | }; |
| 455 | 456 | |
| 456 | 457 | /* Each typecast consumes nested level. So the max number of typecast is 3. */ |
| @@ -594,7 +595,9 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call, · 137 unmodified lines | ||
| 594 | 595 | C(TYPECAST_NOT_EVENT, "Typecasts are only for eprobe fields"), \ |
| 595 | 596 | C(TYPECAST_REQ_FIELD, "Typecast requires a field access"), \ |
| 596 | 597 | C(TOO_MANY_NESTED, "Too many nested typecasts/dereferences"), \ |
| 597 | − C(TYPECAST_SYM_OFFSET, "@SYM+/-OFFSET with typecast needs parentheses") | |
| 598 | + C(TYPECAST_SYM_OFFSET, "@SYM+/-OFFSET with typecast needs parentheses"), \ | |
| 599 | + C(TYPECAST_NOT_ALIGNED, "Typecast field option is not byte-aligned"), \ | |
| 600 | + C(TYPECAST_BAD_ARROW, "Typecast field option does not support -> operator"), | |
| 598 | 601 | |
| 599 | 602 | #undef C |
| 600 | 603 | #define C(a, b) TP_ERR_##a |