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