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