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.

tracing: Add a tracepoint verification check at build time

If a tracepoint is defined via DECLARE_TRACE() or TRACE_EVENT() but never
called (via the trace_<tracepoint>() function), its metadata is still
around in memory and not discarded.

When created via TRACE_EVENT() the situation is worse because the
TRACE_EVENT() creates metadata that can be around 5k per trace event.
Having unused trace events causes several thousand of wasted bytes.

Add a verifier that injects a string of the name of the tracepoint it
calls that is added to the discarded section "__tracepoint_check".
For every builtin tracepoint, its name (which is saved in the in-memory
section "__tracepoint_strings") will have its name also in the
"__tracepoint_check" section if it is used.

Add a new program that is run on build called tracepoint-update. This is
executed on the vmlinux.o before the __tracepoint_check section is
discarded (the section is discarded before vmlinux is created). This
program will create an array of each string in the __tracepoint_check
section and then sort it. Then it will walk the strings in the
__tracepoint_strings section and do a binary search to check if its name
is in the __tracepoint_check section. If it is not, then it is unused and
a warning is printed.

Note, this currently only handles tracepoints that are builtin and not in
modules.

Enabling this currently with a given config produces:

warning: tracepoint 'sched_move_numa' is unused.
warning: tracepoint 'sched_stick_numa' is unused.
warning: tracepoint 'sched_swap_numa' is unused.
warning: tracepoint 'pelt_hw_tp' is unused.
warning: tracepoint 'pelt_irq_tp' is unused.
warning: tracepoint 'rcu_preempt_task' is unused.
warning: tracepoint 'rcu_unlock_preempted_task' is unused.
warning: tracepoint 'xdp_bulk_tx' is unused.
warning: tracepoint 'xdp_redirect_map' is unused.
warning: tracepoint 'xdp_redirect_map_err' is unused.
warning: tracepoint 'vma_mas_szero' is unused.
warning: tracepoint 'vma_store' is unused.
warning: tracepoint 'hugepage_set_pmd' is unused.
warning: tracepoint 'hugepage_set_pud' is unused.
warning: tracepoint 'hugepage_update_pmd' is unused.
warning: tracepoint 'hugepage_update_pud' is unused.
warning: tracepoint 'block_rq_remap' is unused.
warning: tracepoint 'xhci_dbc_handle_event' is unused.
warning: tracepoint 'xhci_dbc_handle_transfer' is unused.
warning: tracepoint 'xhci_dbc_gadget_ep_queue' is unused.
warning: tracepoint 'xhci_dbc_alloc_request' is unused.
warning: tracepoint 'xhci_dbc_free_request' is unused.
warning: tracepoint 'xhci_dbc_queue_request' is unused.
warning: tracepoint 'xhci_dbc_giveback_request' is unused.
warning: tracepoint 'tcp_ao_wrong_maclen' is unused.
warning: tracepoint 'tcp_ao_mismatch' is unused.
warning: tracepoint 'tcp_ao_key_not_found' is unused.
warning: tracepoint 'tcp_ao_rnext_request' is unused.
warning: tracepoint 'tcp_ao_synack_no_key' is unused.
warning: tracepoint 'tcp_ao_snd_sne_update' is unused.
warning: tracepoint 'tcp_ao_rcv_sne_update' is unused.

Some of the above is totally unused but others are not used due to their
"trace_" functions being inside configs, in which case, the defined
tracepoints should also be inside those same configs. Others are
architecture specific but defined in generic code, where they should
either be moved to the architecture or be surrounded by #ifdef for the
architectures they are for.

This tool could be updated to process modules in the future.

I'd like to thank Mathieu Desnoyers for suggesting using strings instead
of pointers, as using pointers in vmlinux.o required handling relocations
and it required implementing almost a full feature linker to do so.

To enable this check, run the build with: make UT=1

Note, when all the existing unused tracepoints are removed from the build,
the "UT=1" will be removed and this will always be enabled when
tracepoints are configured to warn on any new tracepoints. The reason this
isn't always enabled now is because it will introduce a lot of warnings
for the current unused tracepoints, and all bisects would end at this
commit for those warnings.

Link: https://lore.kernel.org/all/20250528114549.4d8a5e03@gandalf.local.home/

Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Masahiro Yamada <masahiroy@kernel.org>
Cc: Nathan Chancellor <nathan@kernel.org>
Cc: Nicolas Schier <nicolas.schier@linux.dev>
Cc: Nick Desaulniers <nick.desaulniers+lkml@gmail.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Randy Dunlap <rdunlap@infradead.org>
Cc: Stephen Rothwell <sfr@canb.auug.org.au>
Link: https://lore.kernel.org/20251022004452.920728129@kernel.org
Suggested-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com> # for using strings instead of pointers
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>

+275
+21
Makefile
··· 810 810 CC_FLAGS_FTRACE := -pg 811 811 endif 812 812 813 + ifdef CONFIG_TRACEPOINTS 814 + # To check for unused tracepoints (tracepoints that are defined but never 815 + # called), run with: 816 + # 817 + # make UT=1 818 + # 819 + # Each unused tracepoints can take up to 5KB of memory in the running kernel. 820 + # It is best to remove any that are not used. 821 + # 822 + # This command line option will be removed when all current unused 823 + # tracepoints are removed. 824 + 825 + ifeq ("$(origin UT)", "command line") 826 + WARN_ON_UNUSED_TRACEPOINTS := $(UT) 827 + endif 828 + endif # CONFIG_TRACEPOINTS 829 + 830 + export WARN_ON_UNUSED_TRACEPOINTS 831 + 813 832 include $(srctree)/arch/$(SRCARCH)/Makefile 814 833 815 834 ifdef need-config ··· 1791 1772 @echo ' c: extra checks in the configuration stage (Kconfig)' 1792 1773 @echo ' e: warnings are being treated as errors' 1793 1774 @echo ' Multiple levels can be combined with W=12 or W=123' 1775 + @echo ' make UT=1 [targets] Warn if a tracepoint is defined but not used.' 1776 + @echo ' [ This will be removed when all current unused tracepoints are eliminated. ]' 1794 1777 @$(if $(dtstree), \ 1795 1778 echo ' make CHECK_DTBS=1 [targets] Check all generated dtb files against schema'; \ 1796 1779 echo ' This can be applied both to "dtbs" and to individual "foo.dtb" targets' ; \
+1
include/asm-generic/vmlinux.lds.h
··· 1048 1048 *(.no_trim_symbol) \ 1049 1049 /* ld.bfd warns about .gnu.version* even when not emitted */ \ 1050 1050 *(.gnu.version*) \ 1051 + *(__tracepoint_check) \ 1051 1052 1052 1053 #define DISCARDS \ 1053 1054 /DISCARD/ : { \
+11
include/linux/tracepoint.h
··· 222 222 } 223 223 224 224 /* 225 + * When a tracepoint is used, it's name is added to the __tracepoint_check 226 + * section. This section is only used at build time to make sure all 227 + * defined tracepoints are used. It is discarded after the build. 228 + */ 229 + # define TRACEPOINT_CHECK(name) \ 230 + static const char __used __section("__tracepoint_check") __trace_check[] = \ 231 + #name; 232 + 233 + /* 225 234 * Make sure the alignment of the structure in the __tracepoints section will 226 235 * not add unwanted padding between the beginning of the section and the 227 236 * structure. Force alignment to the same alignment as the section start. ··· 279 270 __DECLARE_TRACE_COMMON(name, PARAMS(proto), PARAMS(args), PARAMS(data_proto)) \ 280 271 static inline void __do_trace_##name(proto) \ 281 272 { \ 273 + TRACEPOINT_CHECK(name) \ 282 274 if (cond) { \ 283 275 guard(preempt_notrace)(); \ 284 276 __DO_TRACE_CALL(name, TP_ARGS(args)); \ ··· 299 289 __DECLARE_TRACE_COMMON(name, PARAMS(proto), PARAMS(args), PARAMS(data_proto)) \ 300 290 static inline void __do_trace_##name(proto) \ 301 291 { \ 292 + TRACEPOINT_CHECK(name) \ 302 293 guard(rcu_tasks_trace)(); \ 303 294 __DO_TRACE_CALL(name, TP_ARGS(args)); \ 304 295 } \
+3
scripts/Makefile
··· 11 11 hostprogs-always-$(CONFIG_SYSTEM_EXTRA_CERTIFICATE) += insert-sys-cert 12 12 hostprogs-always-$(CONFIG_RUST_KERNEL_DOCTESTS) += rustdoc_test_builder 13 13 hostprogs-always-$(CONFIG_RUST_KERNEL_DOCTESTS) += rustdoc_test_gen 14 + hostprogs-always-$(CONFIG_TRACEPOINTS) += tracepoint-update 14 15 15 16 sorttable-objs := sorttable.o elf-parse.o 17 + tracepoint-update-objs := tracepoint-update.o elf-parse.o 16 18 17 19 ifneq ($(or $(CONFIG_X86_64),$(CONFIG_X86_32)),) 18 20 always-$(CONFIG_RUST) += target.json ··· 29 27 rustdoc_test_builder-rust := y 30 28 rustdoc_test_gen-rust := y 31 29 30 + HOSTCFLAGS_tracepoint-update.o = -I$(srctree)/tools/include 32 31 HOSTCFLAGS_elf-parse.o = -I$(srctree)/tools/include 33 32 HOSTCFLAGS_sorttable.o = -I$(srctree)/tools/include 34 33 HOSTLDLIBS_sorttable = -lpthread
+232
scripts/tracepoint-update.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + 3 + #include <sys/types.h> 4 + #include <sys/stat.h> 5 + #include <getopt.h> 6 + #include <fcntl.h> 7 + #include <stdio.h> 8 + #include <stdlib.h> 9 + #include <stdbool.h> 10 + #include <string.h> 11 + #include <unistd.h> 12 + #include <errno.h> 13 + #include <pthread.h> 14 + 15 + #include "elf-parse.h" 16 + 17 + static Elf_Shdr *check_data_sec; 18 + static Elf_Shdr *tracepoint_data_sec; 19 + 20 + static inline void *get_index(void *start, int entsize, int index) 21 + { 22 + return start + (entsize * index); 23 + } 24 + 25 + static int compare_strings(const void *a, const void *b) 26 + { 27 + const char *av = *(const char **)a; 28 + const char *bv = *(const char **)b; 29 + 30 + return strcmp(av, bv); 31 + } 32 + 33 + struct elf_tracepoint { 34 + Elf_Ehdr *ehdr; 35 + const char **array; 36 + int count; 37 + }; 38 + 39 + #define REALLOC_SIZE (1 << 10) 40 + #define REALLOC_MASK (REALLOC_SIZE - 1) 41 + 42 + static int add_string(const char *str, const char ***vals, int *count) 43 + { 44 + const char **array = *vals; 45 + 46 + if (!(*count & REALLOC_MASK)) { 47 + int size = (*count) + REALLOC_SIZE; 48 + 49 + array = realloc(array, sizeof(char *) * size); 50 + if (!array) { 51 + fprintf(stderr, "Failed memory allocation\n"); 52 + return -1; 53 + } 54 + *vals = array; 55 + } 56 + 57 + array[(*count)++] = str; 58 + return 0; 59 + } 60 + 61 + /** 62 + * for_each_shdr_str - iterator that reads strings that are in an ELF section. 63 + * @len: "int" to hold the length of the current string 64 + * @ehdr: A pointer to the ehdr of the ELF file 65 + * @sec: The section that has the strings to iterate on 66 + * 67 + * This is a for loop that iterates over all the nul terminated strings 68 + * that are in a given ELF section. The variable "str" will hold 69 + * the current string for each iteration and the passed in @len will 70 + * contain the strlen() of that string. 71 + */ 72 + #define for_each_shdr_str(len, ehdr, sec) \ 73 + for (const char *str = (void *)(ehdr) + shdr_offset(sec), \ 74 + *end = str + shdr_size(sec); \ 75 + len = strlen(str), str < end; \ 76 + str += (len) + 1) 77 + 78 + 79 + static void make_trace_array(struct elf_tracepoint *etrace) 80 + { 81 + Elf_Ehdr *ehdr = etrace->ehdr; 82 + const char **vals = NULL; 83 + int count = 0; 84 + int len; 85 + 86 + etrace->array = NULL; 87 + 88 + /* 89 + * The __tracepoint_check section is filled with strings of the 90 + * names of tracepoints (in tracepoint_strings). Create an array 91 + * that points to each string and then sort the array. 92 + */ 93 + for_each_shdr_str(len, ehdr, check_data_sec) { 94 + if (!len) 95 + continue; 96 + if (add_string(str, &vals, &count) < 0) 97 + return; 98 + } 99 + 100 + /* If CONFIG_TRACEPOINT_VERIFY_USED is not set, there's nothing to do */ 101 + if (!count) 102 + return; 103 + 104 + qsort(vals, count, sizeof(char *), compare_strings); 105 + 106 + etrace->array = vals; 107 + etrace->count = count; 108 + } 109 + 110 + static int find_event(const char *str, void *array, size_t size) 111 + { 112 + return bsearch(&str, array, size, sizeof(char *), compare_strings) != NULL; 113 + } 114 + 115 + static void check_tracepoints(struct elf_tracepoint *etrace) 116 + { 117 + Elf_Ehdr *ehdr = etrace->ehdr; 118 + int len; 119 + 120 + if (!etrace->array) 121 + return; 122 + 123 + /* 124 + * The __tracepoints_strings section holds all the names of the 125 + * defined tracepoints. If any of them are not in the 126 + * __tracepoint_check_section it means they are not used. 127 + */ 128 + for_each_shdr_str(len, ehdr, tracepoint_data_sec) { 129 + if (!len) 130 + continue; 131 + if (!find_event(str, etrace->array, etrace->count)) { 132 + fprintf(stderr, "warning: tracepoint '%s' is unused.\n", str); 133 + } 134 + } 135 + 136 + free(etrace->array); 137 + } 138 + 139 + static void *tracepoint_check(struct elf_tracepoint *etrace) 140 + { 141 + make_trace_array(etrace); 142 + check_tracepoints(etrace); 143 + 144 + return NULL; 145 + } 146 + 147 + static int process_tracepoints(void *addr, char const *const fname) 148 + { 149 + struct elf_tracepoint etrace = {0}; 150 + Elf_Ehdr *ehdr = addr; 151 + Elf_Shdr *shdr_start; 152 + Elf_Shdr *string_sec; 153 + const char *secstrings; 154 + unsigned int shnum; 155 + unsigned int shstrndx; 156 + int shentsize; 157 + int idx; 158 + int done = 2; 159 + 160 + shdr_start = (Elf_Shdr *)((char *)ehdr + ehdr_shoff(ehdr)); 161 + shentsize = ehdr_shentsize(ehdr); 162 + 163 + shstrndx = ehdr_shstrndx(ehdr); 164 + if (shstrndx == SHN_XINDEX) 165 + shstrndx = shdr_link(shdr_start); 166 + string_sec = get_index(shdr_start, shentsize, shstrndx); 167 + secstrings = (const char *)ehdr + shdr_offset(string_sec); 168 + 169 + shnum = ehdr_shnum(ehdr); 170 + if (shnum == SHN_UNDEF) 171 + shnum = shdr_size(shdr_start); 172 + 173 + for (int i = 0; done && i < shnum; i++) { 174 + Elf_Shdr *shdr = get_index(shdr_start, shentsize, i); 175 + 176 + idx = shdr_name(shdr); 177 + 178 + /* locate the __tracepoint_check in vmlinux */ 179 + if (!strcmp(secstrings + idx, "__tracepoint_check")) { 180 + check_data_sec = shdr; 181 + done--; 182 + } 183 + 184 + /* locate the __tracepoints_ptrs section in vmlinux */ 185 + if (!strcmp(secstrings + idx, "__tracepoints_strings")) { 186 + tracepoint_data_sec = shdr; 187 + done--; 188 + } 189 + } 190 + 191 + if (!check_data_sec) { 192 + fprintf(stderr, "no __tracepoint_check in file: %s\n", fname); 193 + return -1; 194 + } 195 + 196 + if (!tracepoint_data_sec) { 197 + fprintf(stderr, "no __tracepoint_strings in file: %s\n", fname); 198 + return -1; 199 + } 200 + 201 + etrace.ehdr = ehdr; 202 + tracepoint_check(&etrace); 203 + return 0; 204 + } 205 + 206 + int main(int argc, char *argv[]) 207 + { 208 + int n_error = 0; 209 + size_t size = 0; 210 + void *addr = NULL; 211 + 212 + if (argc < 2) { 213 + fprintf(stderr, "usage: tracepoint-update vmlinux...\n"); 214 + return 0; 215 + } 216 + 217 + /* Process each file in turn, allowing deep failure. */ 218 + for (int i = 1; i < argc; i++) { 219 + addr = elf_map(argv[i], &size, 1 << ET_REL); 220 + if (!addr) { 221 + ++n_error; 222 + continue; 223 + } 224 + 225 + if (process_tracepoints(addr, argv[i])) 226 + ++n_error; 227 + 228 + elf_unmap(addr, size); 229 + } 230 + 231 + return !!n_error; 232 + }