Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

objtool: Add option to trace function validation

Add an option to trace and have information during the validation
of specified functions. Functions are specified with the --trace
option which can be a single function name (e.g. --trace foo to
trace the function with the name "foo"), or a shell wildcard
pattern (e.g. --trace foo* to trace all functions with a name
starting with "foo").

Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: Josh Poimboeuf <jpoimboe@kernel.org>
Link: https://patch.msgid.link/20251121095340.464045-11-alexandre.chartre@oracle.com

authored by

Alexandre Chartre and committed by
Peter Zijlstra
70589843 de0248fb

+299 -18
+1
tools/objtool/Build
··· 9 9 objtool-y += objtool.o 10 10 11 11 objtool-$(BUILD_DISAS) += disas.o 12 + objtool-$(BUILD_DISAS) += trace.o 12 13 13 14 objtool-$(BUILD_ORC) += orc_gen.o orc_dump.o 14 15 objtool-$(BUILD_KLP) += builtin-klp.o klp-diff.o klp-post-link.o
+1
tools/objtool/builtin-check.c
··· 103 103 OPT_STRING('o', "output", &opts.output, "file", "output file name"), 104 104 OPT_BOOLEAN(0, "sec-address", &opts.sec_address, "print section addresses in warnings"), 105 105 OPT_BOOLEAN(0, "stats", &opts.stats, "print statistics"), 106 + OPT_STRING(0, "trace", &opts.trace, "func", "trace function validation"), 106 107 OPT_BOOLEAN('v', "verbose", &opts.verbose, "verbose warnings"), 107 108 OPT_BOOLEAN(0, "werror", &opts.werror, "return error on warnings"), 108 109
+87 -17
tools/objtool/check.c
··· 4 4 */ 5 5 6 6 #define _GNU_SOURCE /* memmem() */ 7 + #include <fnmatch.h> 7 8 #include <string.h> 8 9 #include <stdlib.h> 9 10 #include <inttypes.h> ··· 16 15 #include <objtool/disas.h> 17 16 #include <objtool/check.h> 18 17 #include <objtool/special.h> 18 + #include <objtool/trace.h> 19 19 #include <objtool/warn.h> 20 20 #include <objtool/checksum.h> 21 21 #include <objtool/util.h> ··· 39 37 static struct cfi_state func_cfi; 40 38 static struct cfi_state force_undefined_cfi; 41 39 42 - static size_t sym_name_max_len; 40 + struct disas_context *objtool_disas_ctx; 41 + 42 + size_t sym_name_max_len; 43 43 44 44 struct instruction *find_insn(struct objtool_file *file, 45 45 struct section *sec, unsigned long offset) ··· 3560 3556 return false; 3561 3557 3562 3558 /* ANNOTATE_IGNORE_ALTERNATIVE */ 3563 - if (insn->alt_group->ignore) 3559 + if (insn->alt_group->ignore) { 3560 + TRACE_INSN(insn, "alt group ignored"); 3564 3561 return true; 3562 + } 3565 3563 3566 3564 /* 3567 3565 * For NOP patched with CLAC/STAC, only follow the latter to avoid ··· 3669 3663 3670 3664 static int validate_branch(struct objtool_file *file, struct symbol *func, 3671 3665 struct instruction *insn, struct insn_state state); 3666 + static int do_validate_branch(struct objtool_file *file, struct symbol *func, 3667 + struct instruction *insn, struct insn_state state); 3672 3668 3673 3669 static int validate_insn(struct objtool_file *file, struct symbol *func, 3674 3670 struct instruction *insn, struct insn_state *statep, ··· 3692 3684 if (!insn->hint && !insn_cfi_match(insn, &statep->cfi)) 3693 3685 return 1; 3694 3686 3695 - if (insn->visited & visited) 3687 + if (insn->visited & visited) { 3688 + TRACE_INSN(insn, "already visited"); 3696 3689 return 0; 3690 + } 3697 3691 } else { 3698 3692 nr_insns_visited++; 3699 3693 } ··· 3732 3722 * It will be seen later via the 3733 3723 * straight-line path. 3734 3724 */ 3735 - if (!prev_insn) 3725 + if (!prev_insn) { 3726 + TRACE_INSN(insn, "defer restore"); 3736 3727 return 0; 3728 + } 3737 3729 3738 3730 WARN_INSN(insn, "objtool isn't smart enough to handle this CFI save/restore combo"); 3739 3731 return 1; ··· 3763 3751 return 1; 3764 3752 3765 3753 if (insn->alts) { 3754 + int i, num_alts; 3755 + 3756 + num_alts = 0; 3757 + for (alt = insn->alts; alt; alt = alt->next) 3758 + num_alts++; 3759 + 3760 + i = 1; 3766 3761 for (alt = insn->alts; alt; alt = alt->next) { 3762 + TRACE_INSN(insn, "alternative %d/%d", i, num_alts); 3767 3763 ret = validate_branch(file, func, alt->insn, *statep); 3768 3764 if (ret) { 3769 3765 BT_INSN(insn, "(alt)"); 3770 3766 return ret; 3771 3767 } 3768 + i++; 3772 3769 } 3770 + TRACE_INSN(insn, "alternative DEFAULT"); 3773 3771 } 3774 3772 3775 3773 if (skip_alt_group(insn)) ··· 3791 3769 switch (insn->type) { 3792 3770 3793 3771 case INSN_RETURN: 3772 + TRACE_INSN(insn, "return"); 3794 3773 return validate_return(func, insn, statep); 3795 3774 3796 3775 case INSN_CALL: 3797 3776 case INSN_CALL_DYNAMIC: 3777 + if (insn->type == INSN_CALL) 3778 + TRACE_INSN(insn, "call"); 3779 + else 3780 + TRACE_INSN(insn, "indirect call"); 3781 + 3798 3782 ret = validate_call(file, insn, statep); 3799 3783 if (ret) 3800 3784 return ret; ··· 3816 3788 case INSN_JUMP_CONDITIONAL: 3817 3789 case INSN_JUMP_UNCONDITIONAL: 3818 3790 if (is_sibling_call(insn)) { 3791 + TRACE_INSN(insn, "sibling call"); 3819 3792 ret = validate_sibling_call(file, insn, statep); 3820 3793 if (ret) 3821 3794 return ret; 3822 3795 3823 3796 } else if (insn->jump_dest) { 3824 - ret = validate_branch(file, func, 3825 - insn->jump_dest, *statep); 3797 + if (insn->type == INSN_JUMP_UNCONDITIONAL) 3798 + TRACE_INSN(insn, "unconditional jump"); 3799 + else 3800 + TRACE_INSN(insn, "jump taken"); 3801 + 3802 + ret = validate_branch(file, func, insn->jump_dest, *statep); 3826 3803 if (ret) { 3827 3804 BT_INSN(insn, "(branch)"); 3828 3805 return ret; ··· 3837 3804 if (insn->type == INSN_JUMP_UNCONDITIONAL) 3838 3805 return 0; 3839 3806 3807 + TRACE_INSN(insn, "jump not taken"); 3840 3808 break; 3841 3809 3842 3810 case INSN_JUMP_DYNAMIC: 3843 3811 case INSN_JUMP_DYNAMIC_CONDITIONAL: 3812 + TRACE_INSN(insn, "indirect jump"); 3844 3813 if (is_sibling_call(insn)) { 3845 3814 ret = validate_sibling_call(file, insn, statep); 3846 3815 if (ret) ··· 3855 3820 break; 3856 3821 3857 3822 case INSN_SYSCALL: 3823 + TRACE_INSN(insn, "syscall"); 3858 3824 if (func && (!next_insn || !next_insn->hint)) { 3859 3825 WARN_INSN(insn, "unsupported instruction in callable function"); 3860 3826 return 1; ··· 3864 3828 break; 3865 3829 3866 3830 case INSN_SYSRET: 3831 + TRACE_INSN(insn, "sysret"); 3867 3832 if (func && (!next_insn || !next_insn->hint)) { 3868 3833 WARN_INSN(insn, "unsupported instruction in callable function"); 3869 3834 return 1; ··· 3873 3836 return 0; 3874 3837 3875 3838 case INSN_STAC: 3839 + TRACE_INSN(insn, "stac"); 3876 3840 if (!opts.uaccess) 3877 3841 break; 3878 3842 ··· 3886 3848 break; 3887 3849 3888 3850 case INSN_CLAC: 3851 + TRACE_INSN(insn, "clac"); 3889 3852 if (!opts.uaccess) 3890 3853 break; 3891 3854 ··· 3904 3865 break; 3905 3866 3906 3867 case INSN_STD: 3868 + TRACE_INSN(insn, "std"); 3907 3869 if (statep->df) { 3908 3870 WARN_INSN(insn, "recursive STD"); 3909 3871 return 1; ··· 3914 3874 break; 3915 3875 3916 3876 case INSN_CLD: 3877 + TRACE_INSN(insn, "cld"); 3917 3878 if (!statep->df && func) { 3918 3879 WARN_INSN(insn, "redundant CLD"); 3919 3880 return 1; ··· 3927 3886 break; 3928 3887 } 3929 3888 3930 - *dead_end = insn->dead_end; 3889 + if (insn->dead_end) 3890 + TRACE_INSN(insn, "dead end"); 3931 3891 3892 + *dead_end = insn->dead_end; 3932 3893 return 0; 3933 3894 } 3934 3895 ··· 3940 3897 * each instruction and validate all the rules described in 3941 3898 * tools/objtool/Documentation/objtool.txt. 3942 3899 */ 3943 - static int validate_branch(struct objtool_file *file, struct symbol *func, 3944 - struct instruction *insn, struct insn_state state) 3900 + static int do_validate_branch(struct objtool_file *file, struct symbol *func, 3901 + struct instruction *insn, struct insn_state state) 3945 3902 { 3946 3903 struct instruction *next_insn, *prev_insn = NULL; 3947 3904 bool dead_end; ··· 3950 3907 if (func && func->ignore) 3951 3908 return 0; 3952 3909 3953 - while (1) { 3910 + do { 3911 + insn->trace = 0; 3954 3912 next_insn = next_insn_to_validate(file, insn); 3955 3913 3956 3914 if (opts.checksum && func && insn->sec) ··· 3974 3930 3975 3931 ret = validate_insn(file, func, insn, &state, prev_insn, next_insn, 3976 3932 &dead_end); 3977 - if (dead_end) 3978 - break; 3979 3933 3980 - if (!next_insn) { 3934 + if (!insn->trace) { 3935 + if (ret) 3936 + TRACE_INSN(insn, "warning (%d)", ret); 3937 + else 3938 + TRACE_INSN(insn, NULL); 3939 + } 3940 + 3941 + if (!dead_end && !next_insn) { 3981 3942 if (state.cfi.cfa.base == CFI_UNDEFINED) 3982 3943 return 0; 3983 3944 if (file->ignore_unreachables) ··· 3996 3947 3997 3948 prev_insn = insn; 3998 3949 insn = next_insn; 3999 - } 3950 + 3951 + } while (!dead_end); 3952 + 3953 + return ret; 3954 + } 3955 + 3956 + static int validate_branch(struct objtool_file *file, struct symbol *func, 3957 + struct instruction *insn, struct insn_state state) 3958 + { 3959 + int ret; 3960 + 3961 + trace_depth_inc(); 3962 + ret = do_validate_branch(file, func, insn, state); 3963 + trace_depth_dec(); 4000 3964 4001 3965 return ret; 4002 3966 } ··· 4470 4408 if (opts.checksum) 4471 4409 checksum_init(func); 4472 4410 4411 + if (opts.trace && !fnmatch(opts.trace, sym->name, 0)) { 4412 + trace_enable(); 4413 + TRACE("%s: validation begin\n", sym->name); 4414 + } 4415 + 4473 4416 ret = validate_branch(file, func, insn, *state); 4474 4417 if (ret) 4475 4418 BT_INSN(insn, "<=== (sym)"); 4419 + 4420 + TRACE("%s: validation %s\n\n", sym->name, ret ? "failed" : "end"); 4421 + trace_disable(); 4476 4422 4477 4423 if (opts.checksum) 4478 4424 checksum_finish(func); ··· 4893 4823 free(chunk->addr); 4894 4824 } 4895 4825 4896 - static struct disas_context *objtool_disas_ctx; 4897 - 4898 4826 const char *objtool_disas_insn(struct instruction *insn) 4899 4827 { 4900 4828 struct disas_context *dctx = objtool_disas_ctx; ··· 4914 4846 * disassembly context to disassemble instruction or function 4915 4847 * on warning or backtrace. 4916 4848 */ 4917 - if (opts.verbose || opts.backtrace) { 4849 + if (opts.verbose || opts.backtrace || opts.trace) { 4918 4850 disas_ctx = disas_context_create(file); 4851 + if (!disas_ctx) 4852 + opts.trace = false; 4919 4853 objtool_disas_ctx = disas_ctx; 4920 4854 } 4921 4855
+115
tools/objtool/disas.c
··· 308 308 return dctx->result; 309 309 } 310 310 311 + #define DISAS_INSN_OFFSET_SPACE 10 312 + #define DISAS_INSN_SPACE 60 313 + 314 + /* 315 + * Print a message in the instruction flow. If insn is not NULL then 316 + * the instruction address is printed in addition of the message, 317 + * otherwise only the message is printed. In all cases, the instruction 318 + * itself is not printed. 319 + */ 320 + static int disas_vprint(FILE *stream, struct section *sec, unsigned long offset, 321 + int depth, const char *format, va_list ap) 322 + { 323 + const char *addr_str; 324 + int i, n; 325 + int len; 326 + 327 + len = sym_name_max_len + DISAS_INSN_OFFSET_SPACE; 328 + if (depth < 0) { 329 + len += depth; 330 + depth = 0; 331 + } 332 + 333 + n = 0; 334 + 335 + if (sec) { 336 + addr_str = offstr(sec, offset); 337 + n += fprintf(stream, "%6lx: %-*s ", offset, len, addr_str); 338 + free((char *)addr_str); 339 + } else { 340 + len += DISAS_INSN_OFFSET_SPACE + 1; 341 + n += fprintf(stream, "%-*s", len, ""); 342 + } 343 + 344 + /* print vertical bars to show the code flow */ 345 + for (i = 0; i < depth; i++) 346 + n += fprintf(stream, "| "); 347 + 348 + if (format) 349 + n += vfprintf(stream, format, ap); 350 + 351 + return n; 352 + } 353 + 354 + /* 355 + * Print a message in the instruction flow. If insn is not NULL then 356 + * the instruction address is printed in addition of the message, 357 + * otherwise only the message is printed. In all cases, the instruction 358 + * itself is not printed. 359 + */ 360 + void disas_print_info(FILE *stream, struct instruction *insn, int depth, 361 + const char *format, ...) 362 + { 363 + struct section *sec; 364 + unsigned long off; 365 + va_list args; 366 + 367 + if (insn) { 368 + sec = insn->sec; 369 + off = insn->offset; 370 + } else { 371 + sec = NULL; 372 + off = 0; 373 + } 374 + 375 + va_start(args, format); 376 + disas_vprint(stream, sec, off, depth, format, args); 377 + va_end(args); 378 + } 379 + 380 + /* 381 + * Print an instruction address (offset and function), the instruction itself 382 + * and an optional message. 383 + */ 384 + void disas_print_insn(FILE *stream, struct disas_context *dctx, 385 + struct instruction *insn, int depth, 386 + const char *format, ...) 387 + { 388 + char fake_nop_insn[32]; 389 + const char *insn_str; 390 + bool fake_nop; 391 + va_list args; 392 + int len; 393 + 394 + /* 395 + * Alternative can insert a fake nop, sometimes with no 396 + * associated section so nothing to disassemble. 397 + */ 398 + fake_nop = (!insn->sec && insn->type == INSN_NOP); 399 + if (fake_nop) { 400 + snprintf(fake_nop_insn, 32, "<fake nop> (%d bytes)", insn->len); 401 + insn_str = fake_nop_insn; 402 + } else { 403 + disas_insn(dctx, insn); 404 + insn_str = disas_result(dctx); 405 + } 406 + 407 + /* print the instruction */ 408 + len = (depth + 1) * 2 < DISAS_INSN_SPACE ? DISAS_INSN_SPACE - (depth+1) * 2 : 1; 409 + disas_print_info(stream, insn, depth, "%-*s", len, insn_str); 410 + 411 + /* print message if any */ 412 + if (!format) 413 + return; 414 + 415 + if (strcmp(format, "\n") == 0) { 416 + fprintf(stream, "\n"); 417 + return; 418 + } 419 + 420 + fprintf(stream, " - "); 421 + va_start(args, format); 422 + vfprintf(stream, format, args); 423 + va_end(args); 424 + } 425 + 311 426 /* 312 427 * Disassemble a single instruction. Return the size of the instruction. 313 428 */
+1
tools/objtool/include/objtool/builtin.h
··· 41 41 const char *output; 42 42 bool sec_address; 43 43 bool stats; 44 + const char *trace; 44 45 bool verbose; 45 46 bool werror; 46 47 };
+5 -1
tools/objtool/include/objtool/check.h
··· 66 66 visited : 4, 67 67 no_reloc : 1, 68 68 hole : 1, 69 - fake : 1; 69 + fake : 1, 70 + trace : 1; 70 71 /* 9 bit hole */ 71 72 72 73 struct alt_group *alt_group; ··· 143 142 insn = next_insn_same_sec(file, insn)) 144 143 145 144 const char *objtool_disas_insn(struct instruction *insn); 145 + 146 + extern size_t sym_name_max_len; 147 + extern struct disas_context *objtool_disas_ctx; 146 148 147 149 #endif /* _CHECK_H */
+11
tools/objtool/include/objtool/disas.h
··· 19 19 const char *options); 20 20 size_t disas_insn(struct disas_context *dctx, struct instruction *insn); 21 21 char *disas_result(struct disas_context *dctx); 22 + void disas_print_info(FILE *stream, struct instruction *insn, int depth, 23 + const char *format, ...); 24 + void disas_print_insn(FILE *stream, struct disas_context *dctx, 25 + struct instruction *insn, int depth, 26 + const char *format, ...); 22 27 23 28 #else /* DISAS */ 24 29 ··· 55 50 { 56 51 return NULL; 57 52 } 53 + 54 + static inline void disas_print_info(FILE *stream, struct instruction *insn, 55 + int depth, const char *format, ...) {} 56 + static inline void disas_print_insn(FILE *stream, struct disas_context *dctx, 57 + struct instruction *insn, int depth, 58 + const char *format, ...) {} 58 59 59 60 #endif /* DISAS */ 60 61
+68
tools/objtool/include/objtool/trace.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0-or-later */ 2 + /* 3 + * Copyright (c) 2025, Oracle and/or its affiliates. 4 + */ 5 + 6 + #ifndef _TRACE_H 7 + #define _TRACE_H 8 + 9 + #include <objtool/check.h> 10 + #include <objtool/disas.h> 11 + 12 + #ifdef DISAS 13 + 14 + extern bool trace; 15 + extern int trace_depth; 16 + 17 + #define TRACE(fmt, ...) \ 18 + ({ if (trace) \ 19 + fprintf(stderr, fmt, ##__VA_ARGS__); \ 20 + }) 21 + 22 + #define TRACE_INSN(insn, fmt, ...) \ 23 + ({ \ 24 + if (trace) { \ 25 + disas_print_insn(stderr, objtool_disas_ctx, \ 26 + insn, trace_depth - 1, \ 27 + fmt, ##__VA_ARGS__); \ 28 + fprintf(stderr, "\n"); \ 29 + insn->trace = 1; \ 30 + } \ 31 + }) 32 + 33 + static inline void trace_enable(void) 34 + { 35 + trace = true; 36 + trace_depth = 0; 37 + } 38 + 39 + static inline void trace_disable(void) 40 + { 41 + trace = false; 42 + } 43 + 44 + static inline void trace_depth_inc(void) 45 + { 46 + if (trace) 47 + trace_depth++; 48 + } 49 + 50 + static inline void trace_depth_dec(void) 51 + { 52 + if (trace) 53 + trace_depth--; 54 + } 55 + 56 + #else /* DISAS */ 57 + 58 + #define TRACE(fmt, ...) ({}) 59 + #define TRACE_INSN(insn, fmt, ...) ({}) 60 + 61 + static inline void trace_enable(void) {} 62 + static inline void trace_disable(void) {} 63 + static inline void trace_depth_inc(void) {} 64 + static inline void trace_depth_dec(void) {} 65 + 66 + #endif 67 + 68 + #endif /* _TRACE_H */
+1
tools/objtool/include/objtool/warn.h
··· 97 97 _len = (_len < 50) ? 50 - _len : 0; \ 98 98 WARN(" %s: " format " %*s%s", _str, ##__VA_ARGS__, _len, "", _istr); \ 99 99 free(_str); \ 100 + __insn->trace = 1; \ 100 101 } \ 101 102 }) 102 103
+9
tools/objtool/trace.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + /* 3 + * Copyright (c) 2025, Oracle and/or its affiliates. 4 + */ 5 + 6 + #include <objtool/trace.h> 7 + 8 + bool trace; 9 + int trace_depth;