xref: /aosp_15_r20/external/bcc/libbpf-tools/bashreadline.c (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
1 /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
2 /* Copyright (c) 2021 Facebook */
3 #include <argp.h>
4 #include <stdio.h>
5 #include <errno.h>
6 #include <signal.h>
7 #include <time.h>
8 #include <unistd.h>
9 #include <ctype.h>
10 
11 #include <bpf/libbpf.h>
12 #include <bpf/bpf.h>
13 #include "bashreadline.h"
14 #include "bashreadline.skel.h"
15 #include "btf_helpers.h"
16 #include "trace_helpers.h"
17 #include "uprobe_helpers.h"
18 
19 #define PERF_BUFFER_PAGES	16
20 #define PERF_POLL_TIMEOUT_MS	100
21 #define warn(...) fprintf(stderr, __VA_ARGS__)
22 
23 static volatile sig_atomic_t exiting = 0;
24 
25 const char *argp_program_version = "bashreadline 1.0";
26 const char *argp_program_bug_address =
27 	"https://github.com/iovisor/bcc/tree/master/libbpf-tools";
28 const char argp_program_doc[] =
29 "Print entered bash commands from all running shells.\n"
30 "\n"
31 "USAGE: bashreadline [-s <path/to/libreadline.so>]\n"
32 "\n"
33 "EXAMPLES:\n"
34 "    bashreadline\n"
35 "    bashreadline -s /usr/lib/libreadline.so\n";
36 
37 static const struct argp_option opts[] = {
38 	{ "shared", 's', "PATH", 0, "the location of libreadline.so library" },
39 	{ "verbose", 'v', NULL, 0, "Verbose debug output" },
40 	{ NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" },
41 	{},
42 };
43 
44 static char *libreadline_path = NULL;
45 static bool verbose = false;
46 
parse_arg(int key,char * arg,struct argp_state * state)47 static error_t parse_arg(int key, char *arg, struct argp_state *state)
48 {
49 	switch (key) {
50 	case 's':
51 		libreadline_path = strdup(arg);
52 		if (libreadline_path == NULL)
53 			return ARGP_ERR_UNKNOWN;
54 		break;
55 	case 'v':
56 		verbose = true;
57 		break;
58 	case 'h':
59 		argp_state_help(state, stderr, ARGP_HELP_STD_HELP);
60 		break;
61 	default:
62 		return ARGP_ERR_UNKNOWN;
63 	}
64 	return 0;
65 }
66 
libbpf_print_fn(enum libbpf_print_level level,const char * format,va_list args)67 static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
68 {
69 	if (level == LIBBPF_DEBUG && !verbose)
70 		return 0;
71 	return vfprintf(stderr, format, args);
72 }
73 
handle_event(void * ctx,int cpu,void * data,__u32 data_size)74 static void handle_event(void *ctx, int cpu, void *data, __u32 data_size)
75 {
76 	struct str_t *e = data;
77 	struct tm *tm;
78 	char ts[16];
79 	time_t t;
80 
81 	time(&t);
82 	tm = localtime(&t);
83 	strftime(ts, sizeof(ts), "%H:%m:%S", tm);
84 
85 	printf("%-9s %-7d %s\n", ts, e->pid, e->str);
86 }
87 
handle_lost_events(void * ctx,int cpu,__u64 lost_cnt)88 static void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt)
89 {
90 	warn("lost %llu events on CPU #%d\n", lost_cnt, cpu);
91 }
92 
find_readline_so()93 static char *find_readline_so()
94 {
95 	const char *bash_path = "/bin/bash";
96 	FILE *fp;
97 	off_t func_off;
98 	char *line = NULL;
99 	size_t line_sz = 0;
100 	char path[128];
101 	char *result = NULL;
102 
103 	func_off = get_elf_func_offset(bash_path, "readline");
104 	if (func_off >= 0)
105 		return strdup(bash_path);
106 
107 	/*
108 	 * Try to find libreadline.so if readline is not defined in
109 	 * bash itself.
110 	 *
111 	 * ldd will print a list of names of shared objects,
112 	 * dependencies, and their paths.  The line for libreadline
113 	 * would looks like
114 	 *
115 	 *      libreadline.so.8 => /usr/lib/libreadline.so.8 (0x00007b....)
116 	 *
117 	 * Here, it finds a line with libreadline.so and extracts the
118 	 * path after the arrow, '=>', symbol.
119 	 */
120 	fp = popen("ldd /bin/bash", "r");
121 	if (fp == NULL)
122 		goto cleanup;
123 	while (getline(&line, &line_sz, fp) >= 0) {
124 		if (sscanf(line, "%*s => %127s", path) < 1)
125 			continue;
126 		if (strstr(line, "/libreadline.so")) {
127 			result = strdup(path);
128 			break;
129 		}
130 	}
131 
132 cleanup:
133 	if (line)
134 		free(line);
135 	if (fp)
136 		pclose(fp);
137 	return result;
138 }
139 
sig_int(int signo)140 static void sig_int(int signo)
141 {
142 	exiting = 1;
143 }
144 
main(int argc,char ** argv)145 int main(int argc, char **argv)
146 {
147 	LIBBPF_OPTS(bpf_object_open_opts, open_opts);
148 	static const struct argp argp = {
149 		.options = opts,
150 		.parser = parse_arg,
151 		.doc = argp_program_doc,
152 	};
153 	struct bashreadline_bpf *obj = NULL;
154 	struct perf_buffer *pb = NULL;
155 	char *readline_so_path;
156 	off_t func_off;
157 	int err;
158 
159 	err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
160 	if (err)
161 		return err;
162 
163 	if (libreadline_path) {
164 		readline_so_path = libreadline_path;
165 	} else if ((readline_so_path = find_readline_so()) == NULL) {
166 		warn("failed to find readline\n");
167 		return 1;
168 	}
169 
170 	libbpf_set_print(libbpf_print_fn);
171 
172 	err = ensure_core_btf(&open_opts);
173 	if (err) {
174 		warn("failed to fetch necessary BTF for CO-RE: %s\n", strerror(-err));
175 		goto cleanup;
176 	}
177 
178 	obj = bashreadline_bpf__open_opts(&open_opts);
179 	if (!obj) {
180 		warn("failed to open BPF object\n");
181 		goto cleanup;
182 	}
183 
184 	err = bashreadline_bpf__load(obj);
185 	if (err) {
186 		warn("failed to load BPF object: %d\n", err);
187 		goto cleanup;
188 	}
189 
190 	func_off = get_elf_func_offset(readline_so_path, "readline");
191 	if (func_off < 0) {
192 		warn("cound not find readline in %s\n", readline_so_path);
193 		goto cleanup;
194 	}
195 
196 	obj->links.printret = bpf_program__attach_uprobe(obj->progs.printret, true, -1,
197 							 readline_so_path, func_off);
198 	if (!obj->links.printret) {
199 		err = -errno;
200 		warn("failed to attach readline: %d\n", err);
201 		goto cleanup;
202 	}
203 
204 	pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES,
205 			      handle_event, handle_lost_events, NULL, NULL);
206 	if (!pb) {
207 		err = -errno;
208 		warn("failed to open perf buffer: %d\n", err);
209 		goto cleanup;
210 	}
211 
212 	if (signal(SIGINT, sig_int) == SIG_ERR) {
213 		warn("can't set signal handler: %s\n", strerror(errno));
214 		err = 1;
215 		goto cleanup;
216 	}
217 
218 	printf("%-9s %-7s %s\n", "TIME", "PID", "COMMAND");
219 	while (!exiting) {
220 		err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
221 		if (err < 0 && err != -EINTR) {
222 			warn("error polling perf buffer: %s\n", strerror(-err));
223 			goto cleanup;
224 		}
225 		err = 0;
226 	}
227 
228 cleanup:
229 	if (readline_so_path)
230 		free(readline_so_path);
231 	perf_buffer__free(pb);
232 	bashreadline_bpf__destroy(obj);
233 	cleanup_core_btf(&open_opts);
234 
235 	return err != 0;
236 }
237