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