1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Here's a sample kernel module showing the use of fprobe to dump a |
4 | * stack trace and selected registers when kernel_clone() is called. |
5 | * |
6 | * For more information on theory of operation of kprobes, see |
7 | * Documentation/trace/kprobes.rst |
8 | * |
9 | * You will see the trace data in /var/log/messages and on the console |
10 | * whenever kernel_clone() is invoked to create a new process. |
11 | */ |
12 | |
13 | #define pr_fmt(fmt) "%s: " fmt, __func__ |
14 | |
15 | #include <linux/kernel.h> |
16 | #include <linux/module.h> |
17 | #include <linux/fprobe.h> |
18 | #include <linux/sched/debug.h> |
19 | #include <linux/slab.h> |
20 | |
21 | #define BACKTRACE_DEPTH 16 |
22 | #define MAX_SYMBOL_LEN 4096 |
23 | static struct fprobe sample_probe; |
24 | static unsigned long nhit; |
25 | |
26 | static char symbol[MAX_SYMBOL_LEN] = "kernel_clone" ; |
27 | module_param_string(symbol, symbol, sizeof(symbol), 0644); |
28 | MODULE_PARM_DESC(symbol, "Probed symbol(s), given by comma separated symbols or a wildcard pattern." ); |
29 | |
30 | static char nosymbol[MAX_SYMBOL_LEN] = "" ; |
31 | module_param_string(nosymbol, nosymbol, sizeof(nosymbol), 0644); |
32 | MODULE_PARM_DESC(nosymbol, "Not-probed symbols, given by a wildcard pattern." ); |
33 | |
34 | static bool stackdump = true; |
35 | module_param(stackdump, bool, 0644); |
36 | MODULE_PARM_DESC(stackdump, "Enable stackdump." ); |
37 | |
38 | static bool use_trace = false; |
39 | module_param(use_trace, bool, 0644); |
40 | MODULE_PARM_DESC(use_trace, "Use trace_printk instead of printk. This is only for debugging." ); |
41 | |
42 | static void show_backtrace(void) |
43 | { |
44 | unsigned long stacks[BACKTRACE_DEPTH]; |
45 | unsigned int len; |
46 | |
47 | len = stack_trace_save(store: stacks, BACKTRACE_DEPTH, skipnr: 2); |
48 | stack_trace_print(trace: stacks, nr_entries: len, spaces: 24); |
49 | } |
50 | |
51 | static int sample_entry_handler(struct fprobe *fp, unsigned long ip, |
52 | unsigned long ret_ip, |
53 | struct pt_regs *regs, void *data) |
54 | { |
55 | if (use_trace) |
56 | /* |
57 | * This is just an example, no kernel code should call |
58 | * trace_printk() except when actively debugging. |
59 | */ |
60 | trace_printk("Enter <%pS> ip = 0x%p\n" , (void *)ip, (void *)ip); |
61 | else |
62 | pr_info("Enter <%pS> ip = 0x%p\n" , (void *)ip, (void *)ip); |
63 | nhit++; |
64 | if (stackdump) |
65 | show_backtrace(); |
66 | return 0; |
67 | } |
68 | |
69 | static void sample_exit_handler(struct fprobe *fp, unsigned long ip, |
70 | unsigned long ret_ip, struct pt_regs *regs, |
71 | void *data) |
72 | { |
73 | unsigned long rip = ret_ip; |
74 | |
75 | if (use_trace) |
76 | /* |
77 | * This is just an example, no kernel code should call |
78 | * trace_printk() except when actively debugging. |
79 | */ |
80 | trace_printk("Return from <%pS> ip = 0x%p to rip = 0x%p (%pS)\n" , |
81 | (void *)ip, (void *)ip, (void *)rip, (void *)rip); |
82 | else |
83 | pr_info("Return from <%pS> ip = 0x%p to rip = 0x%p (%pS)\n" , |
84 | (void *)ip, (void *)ip, (void *)rip, (void *)rip); |
85 | nhit++; |
86 | if (stackdump) |
87 | show_backtrace(); |
88 | } |
89 | |
90 | static int __init fprobe_init(void) |
91 | { |
92 | char *p, *symbuf = NULL; |
93 | const char **syms; |
94 | int ret, count, i; |
95 | |
96 | sample_probe.entry_handler = sample_entry_handler; |
97 | sample_probe.exit_handler = sample_exit_handler; |
98 | |
99 | if (strchr(symbol, '*')) { |
100 | /* filter based fprobe */ |
101 | ret = register_fprobe(fp: &sample_probe, filter: symbol, |
102 | notfilter: nosymbol[0] == '\0' ? NULL : nosymbol); |
103 | goto out; |
104 | } else if (!strchr(symbol, ',')) { |
105 | symbuf = symbol; |
106 | ret = register_fprobe_syms(fp: &sample_probe, syms: (const char **)&symbuf, num: 1); |
107 | goto out; |
108 | } |
109 | |
110 | /* Comma separated symbols */ |
111 | symbuf = kstrdup(s: symbol, GFP_KERNEL); |
112 | if (!symbuf) |
113 | return -ENOMEM; |
114 | p = symbuf; |
115 | count = 1; |
116 | while ((p = strchr(++p, ',')) != NULL) |
117 | count++; |
118 | |
119 | pr_info("%d symbols found\n" , count); |
120 | |
121 | syms = kcalloc(n: count, size: sizeof(char *), GFP_KERNEL); |
122 | if (!syms) { |
123 | kfree(objp: symbuf); |
124 | return -ENOMEM; |
125 | } |
126 | |
127 | p = symbuf; |
128 | for (i = 0; i < count; i++) |
129 | syms[i] = strsep(&p, "," ); |
130 | |
131 | ret = register_fprobe_syms(fp: &sample_probe, syms, num: count); |
132 | kfree(objp: syms); |
133 | kfree(objp: symbuf); |
134 | out: |
135 | if (ret < 0) |
136 | pr_err("register_fprobe failed, returned %d\n" , ret); |
137 | else |
138 | pr_info("Planted fprobe at %s\n" , symbol); |
139 | |
140 | return ret; |
141 | } |
142 | |
143 | static void __exit fprobe_exit(void) |
144 | { |
145 | unregister_fprobe(fp: &sample_probe); |
146 | |
147 | pr_info("fprobe at %s unregistered. %ld times hit, %ld times missed\n" , |
148 | symbol, nhit, sample_probe.nmissed); |
149 | } |
150 | |
151 | module_init(fprobe_init) |
152 | module_exit(fprobe_exit) |
153 | MODULE_LICENSE("GPL" ); |
154 | |