1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) |
2 | /* Copyright (C) 2018 Netronome Systems, Inc. */ |
3 | |
4 | #ifndef _GNU_SOURCE |
5 | #define _GNU_SOURCE |
6 | #endif |
7 | #include <stdarg.h> |
8 | #include <stdio.h> |
9 | #include <stdlib.h> |
10 | #include <string.h> |
11 | #include <sys/types.h> |
12 | #include <bpf/libbpf.h> |
13 | #include <bpf/libbpf_internal.h> |
14 | |
15 | #include "disasm.h" |
16 | #include "json_writer.h" |
17 | #include "main.h" |
18 | #include "xlated_dumper.h" |
19 | |
20 | static int kernel_syms_cmp(const void *sym_a, const void *sym_b) |
21 | { |
22 | return ((struct kernel_sym *)sym_a)->address - |
23 | ((struct kernel_sym *)sym_b)->address; |
24 | } |
25 | |
26 | void kernel_syms_load(struct dump_data *dd) |
27 | { |
28 | struct kernel_sym *sym; |
29 | char buff[256]; |
30 | void *tmp, *address; |
31 | FILE *fp; |
32 | |
33 | fp = fopen("/proc/kallsyms" , "r" ); |
34 | if (!fp) |
35 | return; |
36 | |
37 | while (fgets(buff, sizeof(buff), fp)) { |
38 | tmp = libbpf_reallocarray(dd->sym_mapping, dd->sym_count + 1, |
39 | sizeof(*dd->sym_mapping)); |
40 | if (!tmp) { |
41 | out: |
42 | free(dd->sym_mapping); |
43 | dd->sym_mapping = NULL; |
44 | fclose(fp); |
45 | return; |
46 | } |
47 | dd->sym_mapping = tmp; |
48 | sym = &dd->sym_mapping[dd->sym_count]; |
49 | |
50 | /* module is optional */ |
51 | sym->module[0] = '\0'; |
52 | /* trim the square brackets around the module name */ |
53 | if (sscanf(buff, "%p %*c %s [%[^]]s" , &address, sym->name, sym->module) < 2) |
54 | continue; |
55 | sym->address = (unsigned long)address; |
56 | if (!strcmp(sym->name, "__bpf_call_base" )) { |
57 | dd->address_call_base = sym->address; |
58 | /* sysctl kernel.kptr_restrict was set */ |
59 | if (!sym->address) |
60 | goto out; |
61 | } |
62 | if (sym->address) |
63 | dd->sym_count++; |
64 | } |
65 | |
66 | fclose(fp); |
67 | |
68 | qsort(dd->sym_mapping, dd->sym_count, |
69 | sizeof(*dd->sym_mapping), kernel_syms_cmp); |
70 | } |
71 | |
72 | void kernel_syms_destroy(struct dump_data *dd) |
73 | { |
74 | free(dd->sym_mapping); |
75 | } |
76 | |
77 | struct kernel_sym *kernel_syms_search(struct dump_data *dd, |
78 | unsigned long key) |
79 | { |
80 | struct kernel_sym sym = { |
81 | .address = key, |
82 | }; |
83 | |
84 | return dd->sym_mapping ? |
85 | bsearch(key: &sym, base: dd->sym_mapping, num: dd->sym_count, |
86 | size: sizeof(*dd->sym_mapping), cmp: kernel_syms_cmp) : NULL; |
87 | } |
88 | |
89 | static void __printf(2, 3) print_insn(void *private_data, const char *fmt, ...) |
90 | { |
91 | va_list args; |
92 | |
93 | va_start(args, fmt); |
94 | vprintf(fmt, args); |
95 | va_end(args); |
96 | } |
97 | |
98 | static void __printf(2, 3) |
99 | print_insn_for_graph(void *private_data, const char *fmt, ...) |
100 | { |
101 | char buf[64], *p; |
102 | va_list args; |
103 | |
104 | va_start(args, fmt); |
105 | vsnprintf(buf, size: sizeof(buf), fmt, args); |
106 | va_end(args); |
107 | |
108 | p = buf; |
109 | while (*p != '\0') { |
110 | if (*p == '\n') { |
111 | memmove(p + 3, p, strlen(buf) + 1 - (p - buf)); |
112 | /* Align each instruction dump row left. */ |
113 | *p++ = '\\'; |
114 | *p++ = 'l'; |
115 | /* Output multiline concatenation. */ |
116 | *p++ = '\\'; |
117 | } else if (*p == '<' || *p == '>' || *p == '|' || *p == '&') { |
118 | memmove(p + 1, p, strlen(buf) + 1 - (p - buf)); |
119 | /* Escape special character. */ |
120 | *p++ = '\\'; |
121 | } |
122 | |
123 | p++; |
124 | } |
125 | |
126 | printf("%s" , buf); |
127 | } |
128 | |
129 | static void __printf(2, 3) |
130 | print_insn_json(void *private_data, const char *fmt, ...) |
131 | { |
132 | unsigned int l = strlen(fmt); |
133 | char chomped_fmt[l]; |
134 | va_list args; |
135 | |
136 | va_start(args, fmt); |
137 | if (l > 0) { |
138 | strncpy(p: chomped_fmt, q: fmt, size: l - 1); |
139 | chomped_fmt[l - 1] = '\0'; |
140 | } |
141 | jsonw_vprintf_enquote(json_wtr, chomped_fmt, args); |
142 | va_end(args); |
143 | } |
144 | |
145 | static const char *print_call_pcrel(struct dump_data *dd, |
146 | struct kernel_sym *sym, |
147 | unsigned long address, |
148 | const struct bpf_insn *insn) |
149 | { |
150 | if (!dd->nr_jited_ksyms) |
151 | /* Do not show address for interpreted programs */ |
152 | snprintf(buf: dd->scratch_buff, size: sizeof(dd->scratch_buff), |
153 | fmt: "%+d" , insn->off); |
154 | else if (sym) |
155 | snprintf(buf: dd->scratch_buff, size: sizeof(dd->scratch_buff), |
156 | fmt: "%+d#%s" , insn->off, sym->name); |
157 | else |
158 | snprintf(buf: dd->scratch_buff, size: sizeof(dd->scratch_buff), |
159 | fmt: "%+d#0x%lx" , insn->off, address); |
160 | return dd->scratch_buff; |
161 | } |
162 | |
163 | static const char *print_call_helper(struct dump_data *dd, |
164 | struct kernel_sym *sym, |
165 | unsigned long address) |
166 | { |
167 | if (sym) |
168 | snprintf(buf: dd->scratch_buff, size: sizeof(dd->scratch_buff), |
169 | fmt: "%s" , sym->name); |
170 | else |
171 | snprintf(buf: dd->scratch_buff, size: sizeof(dd->scratch_buff), |
172 | fmt: "0x%lx" , address); |
173 | return dd->scratch_buff; |
174 | } |
175 | |
176 | static const char *print_call(void *private_data, |
177 | const struct bpf_insn *insn) |
178 | { |
179 | struct dump_data *dd = private_data; |
180 | unsigned long address = dd->address_call_base + insn->imm; |
181 | struct kernel_sym *sym; |
182 | |
183 | if (insn->src_reg == BPF_PSEUDO_CALL && |
184 | (__u32) insn->imm < dd->nr_jited_ksyms && dd->jited_ksyms) |
185 | address = dd->jited_ksyms[insn->imm]; |
186 | |
187 | sym = kernel_syms_search(dd, key: address); |
188 | if (insn->src_reg == BPF_PSEUDO_CALL) |
189 | return print_call_pcrel(dd, sym, address, insn); |
190 | else |
191 | return print_call_helper(dd, sym, address); |
192 | } |
193 | |
194 | static const char *print_imm(void *private_data, |
195 | const struct bpf_insn *insn, |
196 | __u64 full_imm) |
197 | { |
198 | struct dump_data *dd = private_data; |
199 | |
200 | if (insn->src_reg == BPF_PSEUDO_MAP_FD) |
201 | snprintf(buf: dd->scratch_buff, size: sizeof(dd->scratch_buff), |
202 | fmt: "map[id:%u]" , insn->imm); |
203 | else if (insn->src_reg == BPF_PSEUDO_MAP_VALUE) |
204 | snprintf(buf: dd->scratch_buff, size: sizeof(dd->scratch_buff), |
205 | fmt: "map[id:%u][0]+%u" , insn->imm, (insn + 1)->imm); |
206 | else if (insn->src_reg == BPF_PSEUDO_MAP_IDX_VALUE) |
207 | snprintf(buf: dd->scratch_buff, size: sizeof(dd->scratch_buff), |
208 | fmt: "map[idx:%u]+%u" , insn->imm, (insn + 1)->imm); |
209 | else if (insn->src_reg == BPF_PSEUDO_FUNC) |
210 | snprintf(buf: dd->scratch_buff, size: sizeof(dd->scratch_buff), |
211 | fmt: "subprog[%+d]" , insn->imm); |
212 | else |
213 | snprintf(buf: dd->scratch_buff, size: sizeof(dd->scratch_buff), |
214 | fmt: "0x%llx" , (unsigned long long)full_imm); |
215 | return dd->scratch_buff; |
216 | } |
217 | |
218 | void dump_xlated_json(struct dump_data *dd, void *buf, unsigned int len, |
219 | bool opcodes, bool linum) |
220 | { |
221 | const struct bpf_prog_linfo *prog_linfo = dd->prog_linfo; |
222 | const struct bpf_insn_cbs cbs = { |
223 | .cb_print = print_insn_json, |
224 | .cb_call = print_call, |
225 | .cb_imm = print_imm, |
226 | .private_data = dd, |
227 | }; |
228 | struct bpf_func_info *record; |
229 | struct bpf_insn *insn = buf; |
230 | struct btf *btf = dd->btf; |
231 | bool double_insn = false; |
232 | unsigned int nr_skip = 0; |
233 | char func_sig[1024]; |
234 | unsigned int i; |
235 | |
236 | jsonw_start_array(self: json_wtr); |
237 | record = dd->func_info; |
238 | for (i = 0; i < len / sizeof(*insn); i++) { |
239 | if (double_insn) { |
240 | double_insn = false; |
241 | continue; |
242 | } |
243 | double_insn = insn[i].code == (BPF_LD | BPF_IMM | BPF_DW); |
244 | |
245 | jsonw_start_object(self: json_wtr); |
246 | |
247 | if (btf && record) { |
248 | if (record->insn_off == i) { |
249 | btf_dumper_type_only(btf, func_type_id: record->type_id, |
250 | func_only: func_sig, |
251 | size: sizeof(func_sig)); |
252 | if (func_sig[0] != '\0') { |
253 | jsonw_name(self: json_wtr, name: "proto" ); |
254 | jsonw_string(self: json_wtr, value: func_sig); |
255 | } |
256 | record = (void *)record + dd->finfo_rec_size; |
257 | } |
258 | } |
259 | |
260 | if (prog_linfo) { |
261 | const struct bpf_line_info *linfo; |
262 | |
263 | linfo = bpf_prog_linfo__lfind(prog_linfo, i, nr_skip); |
264 | if (linfo) { |
265 | btf_dump_linfo_json(btf, linfo, linum); |
266 | nr_skip++; |
267 | } |
268 | } |
269 | |
270 | jsonw_name(self: json_wtr, name: "disasm" ); |
271 | print_bpf_insn(&cbs, insn + i, true); |
272 | |
273 | if (opcodes) { |
274 | jsonw_name(self: json_wtr, name: "opcodes" ); |
275 | jsonw_start_object(self: json_wtr); |
276 | |
277 | jsonw_name(self: json_wtr, name: "code" ); |
278 | jsonw_printf(self: json_wtr, fmt: "\"0x%02hhx\"" , insn[i].code); |
279 | |
280 | jsonw_name(self: json_wtr, name: "src_reg" ); |
281 | jsonw_printf(self: json_wtr, fmt: "\"0x%hhx\"" , insn[i].src_reg); |
282 | |
283 | jsonw_name(self: json_wtr, name: "dst_reg" ); |
284 | jsonw_printf(self: json_wtr, fmt: "\"0x%hhx\"" , insn[i].dst_reg); |
285 | |
286 | jsonw_name(self: json_wtr, name: "off" ); |
287 | print_hex_data_json(data: (uint8_t *)(&insn[i].off), len: 2); |
288 | |
289 | jsonw_name(self: json_wtr, name: "imm" ); |
290 | if (double_insn && i < len - 1) |
291 | print_hex_data_json(data: (uint8_t *)(&insn[i].imm), |
292 | len: 12); |
293 | else |
294 | print_hex_data_json(data: (uint8_t *)(&insn[i].imm), |
295 | len: 4); |
296 | jsonw_end_object(self: json_wtr); |
297 | } |
298 | jsonw_end_object(self: json_wtr); |
299 | } |
300 | jsonw_end_array(self: json_wtr); |
301 | } |
302 | |
303 | void dump_xlated_plain(struct dump_data *dd, void *buf, unsigned int len, |
304 | bool opcodes, bool linum) |
305 | { |
306 | const struct bpf_prog_linfo *prog_linfo = dd->prog_linfo; |
307 | const struct bpf_insn_cbs cbs = { |
308 | .cb_print = print_insn, |
309 | .cb_call = print_call, |
310 | .cb_imm = print_imm, |
311 | .private_data = dd, |
312 | }; |
313 | struct bpf_func_info *record; |
314 | struct bpf_insn *insn = buf; |
315 | struct btf *btf = dd->btf; |
316 | unsigned int nr_skip = 0; |
317 | bool double_insn = false; |
318 | char func_sig[1024]; |
319 | unsigned int i; |
320 | |
321 | record = dd->func_info; |
322 | for (i = 0; i < len / sizeof(*insn); i++) { |
323 | if (double_insn) { |
324 | double_insn = false; |
325 | continue; |
326 | } |
327 | |
328 | if (btf && record) { |
329 | if (record->insn_off == i) { |
330 | btf_dumper_type_only(btf, func_type_id: record->type_id, |
331 | func_only: func_sig, |
332 | size: sizeof(func_sig)); |
333 | if (func_sig[0] != '\0') |
334 | printf("%s:\n" , func_sig); |
335 | record = (void *)record + dd->finfo_rec_size; |
336 | } |
337 | } |
338 | |
339 | if (prog_linfo) { |
340 | const struct bpf_line_info *linfo; |
341 | |
342 | linfo = bpf_prog_linfo__lfind(prog_linfo, i, nr_skip); |
343 | if (linfo) { |
344 | btf_dump_linfo_plain(btf, linfo, prefix: "; " , |
345 | linum); |
346 | nr_skip++; |
347 | } |
348 | } |
349 | |
350 | double_insn = insn[i].code == (BPF_LD | BPF_IMM | BPF_DW); |
351 | |
352 | printf("% 4d: " , i); |
353 | print_bpf_insn(&cbs, insn + i, true); |
354 | |
355 | if (opcodes) { |
356 | printf(" " ); |
357 | fprint_hex(stdout, insn + i, 8, " " ); |
358 | if (double_insn && i < len - 1) { |
359 | printf(" " ); |
360 | fprint_hex(stdout, insn + i + 1, 8, " " ); |
361 | } |
362 | printf("\n" ); |
363 | } |
364 | } |
365 | } |
366 | |
367 | void dump_xlated_for_graph(struct dump_data *dd, void *buf_start, void *buf_end, |
368 | unsigned int start_idx, |
369 | bool opcodes, bool linum) |
370 | { |
371 | const struct bpf_insn_cbs cbs = { |
372 | .cb_print = print_insn_for_graph, |
373 | .cb_call = print_call, |
374 | .cb_imm = print_imm, |
375 | .private_data = dd, |
376 | }; |
377 | const struct bpf_prog_linfo *prog_linfo = dd->prog_linfo; |
378 | const struct bpf_line_info *last_linfo = NULL; |
379 | struct bpf_func_info *record = dd->func_info; |
380 | struct bpf_insn *insn_start = buf_start; |
381 | struct bpf_insn *insn_end = buf_end; |
382 | struct bpf_insn *cur = insn_start; |
383 | struct btf *btf = dd->btf; |
384 | bool double_insn = false; |
385 | char func_sig[1024]; |
386 | |
387 | for (; cur <= insn_end; cur++) { |
388 | unsigned int insn_off; |
389 | |
390 | if (double_insn) { |
391 | double_insn = false; |
392 | continue; |
393 | } |
394 | double_insn = cur->code == (BPF_LD | BPF_IMM | BPF_DW); |
395 | |
396 | insn_off = (unsigned int)(cur - insn_start + start_idx); |
397 | if (btf && record) { |
398 | if (record->insn_off == insn_off) { |
399 | btf_dumper_type_only(btf, func_type_id: record->type_id, |
400 | func_only: func_sig, |
401 | size: sizeof(func_sig)); |
402 | if (func_sig[0] != '\0') |
403 | printf("; %s:\\l\\\n" , func_sig); |
404 | record = (void *)record + dd->finfo_rec_size; |
405 | } |
406 | } |
407 | |
408 | if (prog_linfo) { |
409 | const struct bpf_line_info *linfo; |
410 | |
411 | linfo = bpf_prog_linfo__lfind(prog_linfo, insn_off, 0); |
412 | if (linfo && linfo != last_linfo) { |
413 | btf_dump_linfo_dotlabel(btf, linfo, linum); |
414 | last_linfo = linfo; |
415 | } |
416 | } |
417 | |
418 | printf("%d: " , insn_off); |
419 | print_bpf_insn(&cbs, cur, true); |
420 | |
421 | if (opcodes) { |
422 | printf("\\ \\ \\ \\ " ); |
423 | fprint_hex(stdout, cur, 8, " " ); |
424 | if (double_insn && cur <= insn_end - 1) { |
425 | printf(" " ); |
426 | fprint_hex(stdout, cur + 1, 8, " " ); |
427 | } |
428 | printf("\\l\\\n" ); |
429 | } |
430 | |
431 | if (cur != insn_end) |
432 | printf("| " ); |
433 | } |
434 | } |
435 | |