/* bpf.c - core analysis suite * * Copyright (C) 2018 David Anderson * Copyright (C) 2018 Red Hat, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "defs.h" struct bpf_info { ulong status; ulong progs, maps; struct list_pair *proglist; struct list_pair *maplist; char *bpf_prog_buf; char *bpf_prog_aux_buf; char *bpf_map_buf; char *bytecode_buf; int bpf_prog_type_size; int bpf_map_map_type_size; int idr_type; #define IDR_ORIG (1) #define IDR_RADIX (2) #define IDR_XARRAY (3) char prog_hdr1[81]; char map_hdr1[81]; } bpf_info = { .status = UNINITIALIZED, }; static void do_bpf(ulong, ulong, ulong, int); static void bpf_init(struct bpf_info *); static int bpf_type_size_init(void); static char *bpf_prog_type_string(int, char *); static char *bpf_map_map_type_string(int, char *); static char *bpf_prog_used_maps(int, char *); static char *bpf_prog_tag_string(char *, char *); static void bpf_prog_gpl_compatible(char *, ulong); static void dump_xlated_plain(void *, unsigned int, int); static void print_boot_time(unsigned long long, char *, unsigned int); static int do_old_idr(int, ulong, struct list_pair *); #define IDR_ORIG_INIT (1) #define IDR_ORIG_COUNT (2) #define IDR_ORIG_GATHER (3) #define PROG_ID (0x1) #define MAP_ID (0x2) #define DUMP_STRUCT (0x4) #define JITED (0x8) #define XLATED (0x10) #define OPCODES (0x20) #define PROG_VERBOSE (0x40) #define MAP_VERBOSE (0x80) void cmd_bpf(void) { int c, radix; ulong flags, prog_id, map_id; flags = prog_id = map_id = radix = 0; while ((c = getopt(argcnt, args, "PMtTjsxdm:p:")) != EOF) { switch(c) { case 'j': flags |= JITED; break; case 'T': flags |= (XLATED|OPCODES); break; case 't': flags |= XLATED; break; case 'm': map_id = stol(optarg, FAULT_ON_ERROR, NULL); flags |= MAP_ID; break; case 'p': prog_id = stol(optarg, FAULT_ON_ERROR, NULL); flags |= PROG_ID; break; case 's': flags |= DUMP_STRUCT; break; case 'P': flags |= PROG_VERBOSE; break; case 'M': flags |= MAP_VERBOSE; break; case 'x': if (radix == 10) error(FATAL, "-d and -x are mutually exclusive\n"); radix = 16; break; case 'd': if (radix == 16) error(FATAL, "-d and -x are mutually exclusive\n"); radix = 16; break; default: argerrs++; break; } } if (argerrs) cmd_usage(pc->curcmd, SYNOPSIS); if ((flags & JITED) && !(flags & (PROG_ID|PROG_VERBOSE))) error(FATAL, "-j option only applicable with -p or -P\n"); if ((flags & XLATED) && !(flags & (PROG_ID|PROG_VERBOSE))) error(FATAL, "-t option only applicable with -p or -P\n"); if ((flags & DUMP_STRUCT) && !(flags & (PROG_ID|PROG_VERBOSE|MAP_ID|MAP_VERBOSE))) error(FATAL, "-s option requires either -p, -P, -m or -M\n"); if (radix && !(flags & (PROG_ID|PROG_VERBOSE|MAP_ID|MAP_VERBOSE))) error(FATAL, "-%c option requires -s\n", radix == 10 ? 'd' : 'x'); while (args[optind]) { error(FATAL, "invalid argument: %s\n", args[optind]); optind++; } do_bpf(flags, prog_id, map_id, radix); } static void bpf_init(struct bpf_info *bpf) { long len; char buf1[BUFSIZE]; char buf2[BUFSIZE]; char buf3[BUFSIZE]; char buf4[BUFSIZE]; switch (bpf->status) { case UNINITIALIZED: if (!kernel_symbol_exists("prog_idr") || !kernel_symbol_exists("map_idr")) { bpf->status = FALSE; command_not_supported(); } STRUCT_SIZE_INIT(bpf_prog, "bpf_prog"); STRUCT_SIZE_INIT(bpf_prog_aux, "bpf_prog_aux"); STRUCT_SIZE_INIT(bpf_map, "bpf_map"); STRUCT_SIZE_INIT(bpf_insn, "bpf_insn"); MEMBER_OFFSET_INIT(bpf_prog_aux, "bpf_prog", "aux"); MEMBER_OFFSET_INIT(bpf_prog_type, "bpf_prog", "type"); MEMBER_OFFSET_INIT(bpf_prog_tag, "bpf_prog", "tag"); MEMBER_OFFSET_INIT(bpf_prog_jited_len, "bpf_prog", "jited_len"); MEMBER_OFFSET_INIT(bpf_prog_bpf_func, "bpf_prog", "bpf_func"); MEMBER_OFFSET_INIT(bpf_prog_len, "bpf_prog", "len"); MEMBER_OFFSET_INIT(bpf_prog_insnsi, "bpf_prog", "insnsi"); MEMBER_OFFSET_INIT(bpf_map_map_type, "bpf_map", "map_type"); MEMBER_OFFSET_INIT(bpf_map_map_flags, "bpf_map", "map_flags"); MEMBER_OFFSET_INIT(bpf_prog_aux_used_maps, "bpf_prog_aux", "used_maps"); MEMBER_OFFSET_INIT(bpf_prog_aux_used_map_cnt, "bpf_prog_aux", "used_map_cnt"); if (!VALID_STRUCT(bpf_prog) || !VALID_STRUCT(bpf_prog_aux) || !VALID_STRUCT(bpf_map) || !VALID_STRUCT(bpf_insn) || INVALID_MEMBER(bpf_prog_aux) || INVALID_MEMBER(bpf_prog_type) || INVALID_MEMBER(bpf_prog_tag) || INVALID_MEMBER(bpf_prog_jited_len) || INVALID_MEMBER(bpf_prog_bpf_func) || INVALID_MEMBER(bpf_prog_len) || INVALID_MEMBER(bpf_prog_insnsi) || INVALID_MEMBER(bpf_map_map_flags) || INVALID_MEMBER(bpf_map_map_type) || INVALID_MEMBER(bpf_prog_aux_used_maps) || INVALID_MEMBER(bpf_prog_aux_used_map_cnt)) { bpf->status = FALSE; command_not_supported(); } /* * Not required for basic functionality */ MEMBER_OFFSET_INIT(bpf_prog_pages, "bpf_prog", "pages"); MEMBER_OFFSET_INIT(bpf_prog_aux_load_time, "bpf_prog_aux", "load_time"); MEMBER_OFFSET_INIT(bpf_prog_aux_user, "bpf_prog_aux", "user"); MEMBER_OFFSET_INIT(bpf_map_key_size, "bpf_map", "key_size"); MEMBER_OFFSET_INIT(bpf_map_value_size, "bpf_map", "value_size"); MEMBER_OFFSET_INIT(bpf_map_max_entries, "bpf_map", "max_entries"); MEMBER_OFFSET_INIT(bpf_map_pages, "bpf_map", "pages"); MEMBER_OFFSET_INIT(bpf_map_name, "bpf_map", "name"); MEMBER_OFFSET_INIT(bpf_map_user, "bpf_map", "user"); MEMBER_OFFSET_INIT(user_struct_uid, "user_struct", "uid"); if (!bpf_type_size_init()) { bpf->status = FALSE; command_not_supported(); } sprintf(bpf->prog_hdr1, "%s %s %s %s ", mkstring(buf1, 4, CENTER|LJUST, "ID"), mkstring(buf2, VADDR_PRLEN, CENTER|LJUST, "BPF_PROG"), mkstring(buf3, VADDR_PRLEN, CENTER|LJUST, "BPF_PROG_AUX"), mkstring(buf4, bpf->bpf_prog_type_size, CENTER|LJUST, "BPF_PROG_TYPE")); strcat(bpf->prog_hdr1, " TAG USED_MAPS"); sprintf(bpf->map_hdr1, "%s %s %s MAP_FLAGS", mkstring(buf1, 4, CENTER|LJUST, "ID"), mkstring(buf2, VADDR_PRLEN, CENTER|LJUST, "BPF_MAP"), mkstring(buf3, bpf->bpf_map_map_type_size, CENTER|LJUST, "BPF_MAP_TYPE")); if (INVALID_MEMBER(idr_idr_rt)) { bpf->idr_type = IDR_ORIG; do_old_idr(IDR_ORIG_INIT, 0, NULL); } else if (STREQ(MEMBER_TYPE_NAME("idr", "idr_rt"), "radix_tree_root")) if (MEMBER_EXISTS("radix_tree_root", "rnode")) bpf->idr_type = IDR_RADIX; else if (MEMBER_EXISTS("radix_tree_root", "xa_head")) bpf->idr_type = IDR_XARRAY; else error(FATAL, "cannot determine IDR list type\n"); else if (STREQ(MEMBER_TYPE_NAME("idr", "idr_rt"), "xarray")) bpf->idr_type = IDR_XARRAY; else error(FATAL, "cannot determine IDR list type\n"); bpf->status = TRUE; break; case TRUE: break; case FALSE: command_not_supported(); } switch (bpf->idr_type) { case IDR_ORIG: bpf->progs = do_old_idr(IDR_ORIG_COUNT, symbol_value("prog_idr"), NULL); break; case IDR_RADIX: bpf->progs = do_radix_tree(symbol_value("prog_idr") + OFFSET(idr_idr_rt), RADIX_TREE_COUNT, NULL); break; case IDR_XARRAY: bpf->progs = do_xarray(symbol_value("prog_idr") + OFFSET(idr_idr_rt), XARRAY_COUNT, NULL); break; } if (bpf->progs) { len = sizeof(struct list_pair) * (bpf->progs+1); bpf->proglist = (struct list_pair *)GETBUF(len); bpf->proglist[0].index = bpf->progs; switch (bpf->idr_type) { case IDR_ORIG: bpf->progs = do_old_idr(IDR_ORIG_GATHER, symbol_value("prog_idr"), bpf->proglist); break; case IDR_RADIX: bpf->progs = do_radix_tree(symbol_value("prog_idr") + OFFSET(idr_idr_rt), RADIX_TREE_GATHER, bpf->proglist); break; case IDR_XARRAY: bpf->progs = do_xarray(symbol_value("prog_idr") + OFFSET(idr_idr_rt), XARRAY_GATHER, bpf->proglist); break; } } switch (bpf->idr_type) { case IDR_ORIG: bpf->maps = do_old_idr(IDR_ORIG_COUNT, symbol_value("map_idr"), NULL); break; case IDR_RADIX: bpf->maps = do_radix_tree(symbol_value("map_idr") + OFFSET(idr_idr_rt), RADIX_TREE_COUNT, NULL); break; case IDR_XARRAY: bpf->maps = do_xarray(symbol_value("map_idr") + OFFSET(idr_idr_rt), XARRAY_COUNT, NULL); break; } if (bpf->maps) { len = sizeof(struct list_pair) * (bpf->maps+1); bpf->maplist = (struct list_pair *)GETBUF(len); bpf->maplist[0].index = bpf->maps; switch (bpf->idr_type) { case IDR_ORIG: bpf->maps = do_old_idr(IDR_ORIG_GATHER, symbol_value("map_idr"), bpf->maplist); break; case IDR_RADIX: bpf->maps = do_radix_tree(symbol_value("map_idr") + OFFSET(idr_idr_rt), RADIX_TREE_GATHER, bpf->maplist); break; case IDR_XARRAY: bpf->maps = do_xarray(symbol_value("map_idr") + OFFSET(idr_idr_rt), XARRAY_GATHER, bpf->maplist); break; } } bpf->bpf_prog_buf = GETBUF(SIZE(bpf_prog)); bpf->bpf_prog_aux_buf = GETBUF(SIZE(bpf_prog_aux)); bpf->bpf_map_buf = GETBUF(SIZE(bpf_map)); } static void do_bpf(ulong flags, ulong prog_id, ulong map_id, int radix) { struct bpf_info *bpf; int i, c, found, entries, type; uint uid, map_pages, key_size, value_size, max_entries; ulong bpf_prog_aux, bpf_func, end_func, addr, insnsi, user; ulong do_progs, do_maps; ulonglong load_time; char *symbol; ushort prog_pages; int jited_len, len; char *arglist[MAXARGS]; char buf1[BUFSIZE]; char buf2[BUFSIZE]; char buf3[BUFSIZE]; char buf4[BUFSIZE]; char buf5[BUFSIZE/2]; bpf = &bpf_info; bpf->proglist = NULL; bpf->maplist = NULL; bpf->bpf_prog_buf = bpf->bpf_prog_aux_buf = bpf->bpf_map_buf = NULL; bpf->bytecode_buf = NULL; bpf_init(bpf); if (flags & PROG_ID) { for (i = found = 0; i < bpf->progs; i++) { if (prog_id == bpf->proglist[i].index) { found++; break; } } if (!found) { error(INFO, "invalid program ID: %ld\n", prog_id); goto bailout; } } if (flags & MAP_ID) { for (i = found = 0; i < bpf->maps; i++) { if (map_id == bpf->maplist[i].index) { found++; break; } } if (!found) { error(INFO, "invalid map ID: %ld\n", map_id); goto bailout; } } if (!(flags & (PROG_ID|PROG_VERBOSE|MAP_ID|MAP_VERBOSE))) do_progs = do_maps = TRUE; else { do_progs = do_maps = FALSE; if (flags & (PROG_ID|PROG_VERBOSE)) do_progs = TRUE; if (flags & (MAP_ID|MAP_VERBOSE)) do_maps = TRUE; } if (!do_progs) goto do_map_only; for (i = entries = 0; i < bpf->progs; i++) { if (bpf->proglist[i].value == 0) continue; if (((flags & (PROG_ID|PROG_VERBOSE)) == PROG_ID) && (prog_id != bpf->proglist[i].index)) continue; if (!readmem((ulong)bpf->proglist[i].value, KVADDR, bpf->bpf_prog_buf, SIZE(bpf_prog), "struct bpf_prog", RETURN_ON_ERROR)) goto bailout; bpf_prog_aux = ULONG(bpf->bpf_prog_buf + OFFSET(bpf_prog_aux)); if (!readmem(bpf_prog_aux, KVADDR, bpf->bpf_prog_aux_buf, SIZE(bpf_prog_aux), "struct bpf_prog_aux", RETURN_ON_ERROR)) goto bailout; if (entries && (flags & PROG_VERBOSE)) fprintf(fp, "\n%s\n", bpf->prog_hdr1); if (entries++ == 0) fprintf(fp, "%s\n", bpf->prog_hdr1); fprintf(fp, "%s %s %s ", mkstring(buf1, 4, CENTER|LJUST|LONG_DEC, MKSTR(bpf->proglist[i].index)), mkstring(buf2, VADDR_PRLEN, CENTER|LJUST|LONG_HEX, MKSTR(bpf->proglist[i].value)), mkstring(buf3, VADDR_PRLEN, CENTER|LJUST|LONG_HEX, MKSTR(bpf_prog_aux))); type = INT(bpf->bpf_prog_buf + OFFSET(bpf_prog_type)); fprintf(fp, "%s ", mkstring(buf1, bpf->bpf_prog_type_size, CENTER|LJUST, bpf_prog_type_string(type, buf2))); fprintf(fp, "%s ", bpf_prog_tag_string(bpf->bpf_prog_buf + OFFSET(bpf_prog_tag), buf1)); fprintf(fp, "%s ", mkstring(buf1, strlen("USED_MAPS"), CENTER|LJUST, bpf_prog_used_maps(i, buf2))); fprintf(fp, "\n"); if (flags & (PROG_ID|PROG_VERBOSE)) { jited_len = UINT(bpf->bpf_prog_buf + OFFSET(bpf_prog_jited_len)); len = UINT(bpf->bpf_prog_buf + OFFSET(bpf_prog_len)); len *= SIZE(bpf_insn); if (VALID_MEMBER(bpf_prog_pages)) { prog_pages = USHORT(bpf->bpf_prog_buf + OFFSET(bpf_prog_pages)); prog_pages *= PAGESIZE(); } else prog_pages = 0; fprintf(fp, " XLATED: %d JITED: %d MEMLOCK: ", len, jited_len); if (VALID_MEMBER(bpf_prog_pages)) { fprintf(fp, "%d\n", prog_pages); } else fprintf(fp, "(unknown)\n"); fprintf(fp, " LOAD_TIME: "); if (VALID_MEMBER(bpf_prog_aux_load_time)) { load_time = ULONGLONG(bpf->bpf_prog_aux_buf + OFFSET(bpf_prog_aux_load_time)); print_boot_time(load_time, buf5, BUFSIZE/2); fprintf(fp, "%s\n", buf5); } else fprintf(fp, "(unknown)\n"); bpf_prog_gpl_compatible(buf1, (ulong)bpf->proglist[i].value); fprintf(fp, " GPL_COMPATIBLE: %s", buf1); fprintf(fp, " UID: "); if (VALID_MEMBER(bpf_prog_aux_user) && VALID_MEMBER(user_struct_uid)) { user = ULONG(bpf->bpf_prog_aux_buf + OFFSET(bpf_prog_aux_user)); if (readmem(user + OFFSET(user_struct_uid), KVADDR, &uid, sizeof(uint), "user_struct.uid", QUIET|RETURN_ON_ERROR)) fprintf(fp, "%d\n", uid); else fprintf(fp, "(unknown)\n"); } else fprintf(fp, "(unknown)\n"); } if (flags & JITED) { fprintf(fp, "\n"); jited_len = UINT(bpf->bpf_prog_buf + OFFSET(bpf_prog_jited_len)); bpf_func = ULONG(bpf->bpf_prog_buf + OFFSET(bpf_prog_bpf_func)); end_func = bpf_func + jited_len; if (jited_len) { open_tmpfile(); pc->curcmd_private = (ulonglong)end_func; sprintf(buf1, "x/%di 0x%lx", jited_len, bpf_func); gdb_pass_through(buf1, NULL, GNU_RETURN_ON_ERROR); rewind(pc->tmpfile); while (fgets(buf1, BUFSIZE, pc->tmpfile)) { strcpy(buf2, strip_linefeeds(buf1)); c = parse_line(buf1, arglist); if (STRNEQ(arglist[0], "0x") && (LASTCHAR(arglist[0]) == ':')) { addr = htol(strip_ending_char(arglist[0], ':'), RETURN_ON_ERROR, NULL); if (addr >= end_func) break; } symbol = NULL; if ((c > 1) && IS_A_NUMBER(arglist[c-1])) { addr = htol(arglist[c-1], RETURN_ON_ERROR, NULL); symbol = value_to_symstr(addr, buf3, radix); if (strlen(symbol)) { sprintf(buf4, "<%s>", symbol); symbol = buf4; } } fprintf(pc->saved_fp, "%s %s\n", buf2, symbol ? symbol : ""); } pc->curcmd_private = 0; close_tmpfile(); } else fprintf(fp, "(program not jited)\n"); } if (flags & XLATED) { fprintf(fp, "\n"); len = UINT(bpf->bpf_prog_buf + OFFSET(bpf_prog_len)); insnsi = (ulong)bpf->proglist[i].value + OFFSET(bpf_prog_insnsi); bpf->bytecode_buf = GETBUF(len * SIZE(bpf_insn)); if (CRASHDEBUG(1)) fprintf(fp, "bytecode_buf: [%lx] len %d * size %ld = %ld from: %lx\n", (ulong)bpf->bytecode_buf, len, SIZE(bpf_insn), len * SIZE(bpf_insn), insnsi); if (!readmem(insnsi, KVADDR, bpf->bytecode_buf, len * SIZE(bpf_insn), "bpf_prog.insnsi contents", RETURN_ON_ERROR)) goto bailout; dump_xlated_plain((void *)bpf->bytecode_buf, len * SIZE(bpf_insn), flags & OPCODES); } if (flags & DUMP_STRUCT) { fprintf(fp, "\n"); dump_struct("bpf_prog", (ulong)bpf->proglist[i].value, radix); fprintf(fp, "\n"); dump_struct("bpf_prog_aux", bpf_prog_aux, radix); } } if (!do_maps) goto bailout; else fprintf(fp, "\n"); do_map_only: for (i = entries = 0; i < bpf->maps; i++) { if (bpf->maplist[i].value == 0) continue; if (((flags & (MAP_ID|MAP_VERBOSE)) == MAP_ID) && (map_id != bpf->maplist[i].index)) continue; if (entries && (flags & MAP_VERBOSE)) fprintf(fp, "\n%s\n", bpf->map_hdr1); if (entries++ == 0) fprintf(fp, "%s\n", bpf->map_hdr1); if (!readmem((ulong)bpf->maplist[i].value, KVADDR, bpf->bpf_map_buf, SIZE(bpf_map), "struct bpf_map", RETURN_ON_ERROR)) goto bailout; fprintf(fp, "%s %s ", mkstring(buf1, 4, CENTER|LJUST|LONG_DEC, MKSTR(bpf->maplist[i].index)), mkstring(buf2, VADDR_PRLEN, CENTER|LJUST|LONG_HEX, MKSTR(bpf->maplist[i].value))); type = INT(bpf->bpf_map_buf + OFFSET(bpf_map_map_type)); fprintf(fp, "%s ", mkstring(buf1, bpf->bpf_map_map_type_size, CENTER|LJUST, bpf_map_map_type_string(type, buf2))); fprintf(fp, " %08x ", UINT(bpf->bpf_map_buf + OFFSET(bpf_map_map_flags))); fprintf(fp, "\n"); if (flags & (MAP_ID|MAP_VERBOSE)) { fprintf(fp, " KEY_SIZE: "); if (VALID_MEMBER(bpf_map_key_size)) { key_size = UINT(bpf->bpf_map_buf + OFFSET(bpf_map_key_size)); fprintf(fp, "%d", key_size); } else fprintf(fp, "(unknown)"); fprintf(fp, " VALUE_SIZE: "); if (VALID_MEMBER(bpf_map_value_size)) { value_size = UINT(bpf->bpf_map_buf + OFFSET(bpf_map_value_size)); fprintf(fp, "%d", value_size); } else fprintf(fp, "(unknown)"); fprintf(fp, " MAX_ENTRIES: "); if (VALID_MEMBER(bpf_map_max_entries)) { max_entries = UINT(bpf->bpf_map_buf + OFFSET(bpf_map_max_entries)); fprintf(fp, "%d", max_entries); } else fprintf(fp, "(unknown)"); fprintf(fp, " MEMLOCK: "); if (VALID_MEMBER(bpf_map_pages)) { map_pages = UINT(bpf->bpf_map_buf + OFFSET(bpf_map_pages)); fprintf(fp, "%d\n", map_pages * PAGESIZE()); } else fprintf(fp, "(unknown)\n"); fprintf(fp, " NAME: "); if (VALID_MEMBER(bpf_map_name)) { BCOPY(&bpf->bpf_map_buf[OFFSET(bpf_map_name)], buf1, 16); buf1[17] = NULLCHAR; if (strlen(buf1)) fprintf(fp, "\"%s\"", buf1); else fprintf(fp, "(unused)"); } else fprintf(fp, "(unknown)\n"); fprintf(fp, " UID: "); if (VALID_MEMBER(bpf_map_user) && VALID_MEMBER(user_struct_uid)) { user = ULONG(bpf->bpf_map_buf + OFFSET(bpf_map_user)); if (readmem(user + OFFSET(user_struct_uid), KVADDR, &uid, sizeof(uint), "user_struct.uid", QUIET|RETURN_ON_ERROR)) fprintf(fp, "%d\n", uid); else fprintf(fp, "(unknown)\n"); } else fprintf(fp, "(unknown)\n"); } if (flags & DUMP_STRUCT) { fprintf(fp, "\n"); dump_struct("bpf_map", (ulong)bpf->maplist[i].value, radix); } } bailout: if (bpf->proglist) FREEBUF(bpf->proglist); if (bpf->maplist) FREEBUF(bpf->maplist); FREEBUF(bpf->bpf_prog_buf); FREEBUF(bpf->bpf_prog_aux_buf); FREEBUF(bpf->bpf_map_buf); if (bpf->bytecode_buf) FREEBUF(bpf->bytecode_buf); } static int bpf_type_size_init(void) { int c ATTRIBUTE_UNUSED; size_t max; char *arglist[MAXARGS]; char buf[BUFSIZE]; struct bpf_info *bpf = &bpf_info; open_tmpfile(); if (dump_enumerator_list("bpf_prog_type")) { max = 0; rewind(pc->tmpfile); while (fgets(buf, BUFSIZE, pc->tmpfile)) { if (!strstr(buf, " = ")) continue; c = parse_line(buf, arglist); if (CRASHDEBUG(1)) fprintf(pc->saved_fp, "%s\n", arglist[0]); max = MAX(max, strlen(arglist[0])); } bpf->bpf_prog_type_size = max - strlen("BPF_PROG_TYPE_"); } else { close_tmpfile(); return FALSE; } /* * Keep bpf program header at 80 columns */ bpf->bpf_prog_type_size = MIN(13, bpf->bpf_prog_type_size); close_tmpfile(); open_tmpfile(); if (dump_enumerator_list("bpf_map_type")) { max = 0; rewind(pc->tmpfile); while (fgets(buf, BUFSIZE, pc->tmpfile)) { if (!strstr(buf, " = ")) continue; c = parse_line(buf, arglist); if (CRASHDEBUG(1)) fprintf(pc->saved_fp, "%s\n", arglist[0]); max = MAX(max, strlen(arglist[0])); } bpf->bpf_map_map_type_size = max - strlen("BPF_PROG_TYPE_"); } else { close_tmpfile(); return FALSE; } close_tmpfile(); return TRUE; } static char * bpf_prog_type_string(int type, char *retbuf) { char *p; int c ATTRIBUTE_UNUSED; char *arglist[MAXARGS]; char buf[BUFSIZE]; retbuf[0] = NULLCHAR; open_tmpfile(); if (dump_enumerator_list("bpf_prog_type")) { rewind(pc->tmpfile); while (fgets(buf, BUFSIZE, pc->tmpfile)) { if (!strstr(buf, " = ")) continue; c = parse_line(buf, arglist); if (atoi(arglist[2]) == type) { p = arglist[0]; p += strlen("BPF_PROG_TYPE_"); strcpy(retbuf, p); break; } } } close_tmpfile(); return retbuf; } static char * bpf_map_map_type_string(int map_type, char *retbuf) { char *p; int c ATTRIBUTE_UNUSED; char *arglist[MAXARGS]; char buf[BUFSIZE]; retbuf[0] = NULLCHAR; open_tmpfile(); if (dump_enumerator_list("bpf_map_type")) { rewind(pc->tmpfile); while (fgets(buf, BUFSIZE, pc->tmpfile)) { if (!strstr(buf, " = ")) continue; c = parse_line(buf, arglist); if (atoi(arglist[2]) == map_type) { p = arglist[0]; p += strlen("BPF_MAP_TYPE_"); strcpy(retbuf, p); break; } } } close_tmpfile(); return retbuf; } static char * bpf_prog_used_maps(int idx, char *retbuf) { int i, m, cnt; struct bpf_info *bpf = &bpf_info; uint used_map_cnt; ulong used_maps, map; retbuf[0] = NULLCHAR; used_map_cnt = UINT(bpf->bpf_prog_aux_buf + OFFSET(bpf_prog_aux_used_map_cnt)); used_maps = ULONG(bpf->bpf_prog_aux_buf + OFFSET(bpf_prog_aux_used_maps)); for (i = cnt = 0; i < used_map_cnt; i++) { if (!readmem(used_maps + (sizeof(ulong)*i), KVADDR, &map, sizeof(ulong), "bpf_prog_aux.used_maps", RETURN_ON_ERROR)) return retbuf; for (m = 0; m < bpf->maps; m++) { if (map == (ulong)bpf->maplist[m].value) { sprintf(&retbuf[strlen(retbuf)], "%s%ld", strlen(retbuf) ? "," : "", bpf->maplist[m].index); } } } return retbuf; } static char * bpf_prog_tag_string(char *tag, char *buf) { int i; buf[0] = NULLCHAR; for (i = 0; i < 8; i++) sprintf(&buf[strlen(buf)], "%02x", (unsigned char)tag[i]); return buf; } static void bpf_prog_gpl_compatible(char *retbuf, ulong bpf_prog) { char buf[BUFSIZE]; sprintf(retbuf, "(unknown)"); open_tmpfile(); sprintf(buf, "p (*(struct bpf_prog *)0x%lx).gpl_compatible", bpf_prog); gdb_pass_through(buf, NULL, GNU_RETURN_ON_ERROR); rewind(pc->tmpfile); while (fgets(buf, BUFSIZE, pc->tmpfile)) { if (strstr(buf, " = 1")) { sprintf(retbuf, "yes"); break; } else if (strstr(buf, " = 0")) { sprintf(retbuf, "no"); break; } } close_tmpfile(); } // #include /* * Taken from: "/usr/include/linux/bpf_common.h" */ /* * bpf_common.h */ /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ #ifndef __LINUX_BPF_COMMON_H__ #define __LINUX_BPF_COMMON_H__ /* Instruction classes */ #define BPF_CLASS(code) ((code) & 0x07) #define BPF_LD 0x00 #define BPF_LDX 0x01 #define BPF_ST 0x02 #define BPF_STX 0x03 #define BPF_ALU 0x04 #define BPF_JMP 0x05 #define BPF_RET 0x06 #define BPF_MISC 0x07 /* ld/ldx fields */ #define BPF_SIZE(code) ((code) & 0x18) #define BPF_W 0x00 /* 32-bit */ #define BPF_H 0x08 /* 16-bit */ #define BPF_B 0x10 /* 8-bit */ /* eBPF BPF_DW 0x18 64-bit */ #define BPF_MODE(code) ((code) & 0xe0) #define BPF_IMM 0x00 #define BPF_ABS 0x20 #define BPF_IND 0x40 #define BPF_MEM 0x60 #define BPF_LEN 0x80 #define BPF_MSH 0xa0 /* alu/jmp fields */ #define BPF_OP(code) ((code) & 0xf0) #define BPF_ADD 0x00 #define BPF_SUB 0x10 #define BPF_MUL 0x20 #define BPF_DIV 0x30 #define BPF_OR 0x40 #define BPF_AND 0x50 #define BPF_LSH 0x60 #define BPF_RSH 0x70 #define BPF_NEG 0x80 #define BPF_MOD 0x90 #define BPF_XOR 0xa0 #define BPF_JA 0x00 #define BPF_JEQ 0x10 #define BPF_JGT 0x20 #define BPF_JGE 0x30 #define BPF_JSET 0x40 #define BPF_SRC(code) ((code) & 0x08) #define BPF_K 0x00 #define BPF_X 0x08 #ifndef BPF_MAXINSNS #define BPF_MAXINSNS 4096 #endif #endif /* __LINUX_BPF_COMMON_H__ */ /* * Taken from: /usr/include/asm-generic/int-ll64.h */ typedef unsigned char __u8; typedef __signed__ short __s16; typedef __signed__ int __s32; /* * Taken from: "/usr/include/linux/bpf.h" */ /* Extended instruction set based on top of classic BPF */ /* instruction classes */ #define BPF_ALU64 0x07 /* alu mode in double word width */ /* ld/ldx fields */ #define BPF_DW 0x18 /* double word (64-bit) */ #define BPF_XADD 0xc0 /* exclusive add */ /* alu/jmp fields */ #define BPF_MOV 0xb0 /* mov reg to reg */ #define BPF_ARSH 0xc0 /* sign extending arithmetic shift right */ /* change endianness of a register */ #define BPF_END 0xd0 /* flags for endianness conversion: */ #define BPF_TO_LE 0x00 /* convert to little-endian */ #define BPF_TO_BE 0x08 /* convert to big-endian */ #define BPF_FROM_LE BPF_TO_LE #define BPF_FROM_BE BPF_TO_BE /* jmp encodings */ #define BPF_JNE 0x50 /* jump != */ #define BPF_JLT 0xa0 /* LT is unsigned, '<' */ #define BPF_JLE 0xb0 /* LE is unsigned, '<=' */ #define BPF_JSGT 0x60 /* SGT is signed '>', GT in x86 */ #define BPF_JSGE 0x70 /* SGE is signed '>=', GE in x86 */ #define BPF_JSLT 0xc0 /* SLT is signed, '<' */ #define BPF_JSLE 0xd0 /* SLE is signed, '<=' */ #define BPF_CALL 0x80 /* function call */ #define BPF_EXIT 0x90 /* function return */ /* Register numbers */ enum { BPF_REG_0 = 0, BPF_REG_1, BPF_REG_2, BPF_REG_3, BPF_REG_4, BPF_REG_5, BPF_REG_6, BPF_REG_7, BPF_REG_8, BPF_REG_9, BPF_REG_10, __MAX_BPF_REG, }; struct bpf_insn { __u8 code; /* opcode */ __u8 dst_reg:4; /* dest register */ __u8 src_reg:4; /* source register */ __s16 off; /* signed offset */ __s32 imm; /* signed immediate constant */ }; /* instruction classes */ #define BPF_ALU64 0x07 /* alu mode in double word width */ /* ld/ldx fields */ #define BPF_DW 0x18 /* double word (64-bit) */ #define BPF_XADD 0xc0 /* exclusive add */ /* alu/jmp fields */ #define BPF_MOV 0xb0 /* mov reg to reg */ #define BPF_ARSH 0xc0 /* sign extending arithmetic shift right */ /* change endianness of a register */ #define BPF_END 0xd0 /* flags for endianness conversion: */ #define BPF_TO_LE 0x00 /* convert to little-endian */ #define BPF_TO_BE 0x08 /* convert to big-endian */ #define BPF_FROM_LE BPF_TO_LE #define BPF_FROM_BE BPF_TO_BE /* when bpf_ldimm64->src_reg == BPF_PSEUDO_MAP_FD, bpf_ldimm64->imm == fd */ #define BPF_PSEUDO_MAP_FD 1 /* when bpf_call->src_reg == BPF_PSEUDO_CALL, bpf_call->imm == pc-relative * offset to another bpf function */ #define BPF_PSEUDO_CALL 1 static void fprint_hex(FILE *, void *, unsigned int, const char *); /* * Taken from: tools/bpf/bpftool/main.c */ void fprint_hex(FILE *f, void *arg, unsigned int n, const char *sep) { unsigned char *data = arg; unsigned int i; for (i = 0; i < n; i++) { const char *pfx = ""; if (!i) /* nothing */; else if (!(i % 16)) fprintf(f, "\n"); else if (!(i % 8)) fprintf(f, " "); else pfx = sep; fprintf(f, "%s%02hhx", i ? pfx : "", data[i]); } } static void dump_bpf_insn(struct bpf_insn *insn) { fprintf(fp, " code: 0x%x / %d\n", insn->code, insn->code); fprintf(fp, " dst_reg: 0x%x / %d\n", insn->dst_reg, insn->dst_reg); fprintf(fp, " src_reg: 0x%x / %d\n", insn->src_reg, insn->src_reg); fprintf(fp, " off: 0x%x / %d\n", insn->off, insn->off); fprintf(fp, " imm: 0x%x / %d\n", insn->imm, insn->imm); } static void print_bpf_insn(struct bpf_insn *, int); /* * Adapted from: "tools/bpf/bpftool/prog.c" */ static void dump_xlated_plain(void *buf, unsigned int len, int opcodes) { struct bpf_insn *insn = buf; int double_insn = FALSE; unsigned int i; for (i = 0; i < len / sizeof(*insn); i++) { if (double_insn) { double_insn = FALSE; continue; } double_insn = insn[i].code == (BPF_LD | BPF_IMM | BPF_DW); fprintf(fp, "% 4d: ", i); print_bpf_insn(insn + i, TRUE); if (opcodes) { fprintf(fp, " "); fprint_hex(fp, insn + i, 8, " "); if (double_insn && i < len - 1) { fprintf(fp, " "); fprint_hex(fp, insn + i + 1, 8, " "); } fprintf(fp, "\n"); } if (CRASHDEBUG(1)) dump_bpf_insn(insn + i); } } /* * Adapted from: kernel/bpf/disasm.c */ const char *const bpf_class_string[8] = { [BPF_LD] = "ld", [BPF_LDX] = "ldx", [BPF_ST] = "st", [BPF_STX] = "stx", [BPF_ALU] = "alu", [BPF_JMP] = "jmp", [BPF_RET] = "BUG", [BPF_ALU64] = "alu64", }; const char *const bpf_alu_string[16] = { [BPF_ADD >> 4] = "+=", [BPF_SUB >> 4] = "-=", [BPF_MUL >> 4] = "*=", [BPF_DIV >> 4] = "/=", [BPF_OR >> 4] = "|=", [BPF_AND >> 4] = "&=", [BPF_LSH >> 4] = "<<=", [BPF_RSH >> 4] = ">>=", [BPF_NEG >> 4] = "neg", [BPF_MOD >> 4] = "%=", [BPF_XOR >> 4] = "^=", [BPF_MOV >> 4] = "=", [BPF_ARSH >> 4] = "s>>=", [BPF_END >> 4] = "endian", }; static const char *const bpf_ldst_string[] = { [BPF_W >> 3] = "u32", [BPF_H >> 3] = "u16", [BPF_B >> 3] = "u8", [BPF_DW >> 3] = "u64", }; static const char *const bpf_jmp_string[16] = { [BPF_JA >> 4] = "jmp", [BPF_JEQ >> 4] = "==", [BPF_JGT >> 4] = ">", [BPF_JLT >> 4] = "<", [BPF_JGE >> 4] = ">=", [BPF_JLE >> 4] = "<=", [BPF_JSET >> 4] = "&", [BPF_JNE >> 4] = "!=", [BPF_JSGT >> 4] = "s>", [BPF_JSLT >> 4] = "s<", [BPF_JSGE >> 4] = "s>=", [BPF_JSLE >> 4] = "s<=", [BPF_CALL >> 4] = "call", [BPF_EXIT >> 4] = "exit", }; typedef unsigned char u8; typedef unsigned int u32; static const char *__func_imm_name(const struct bpf_insn *insn, uint64_t full_imm, char *buff, size_t len) { int m; struct bpf_info *bpf = &bpf_info; for (m = 0; m < bpf->maps; m++) { if (full_imm == (ulong)bpf->maplist[m].value) { sprintf(buff, "map[id:%ld]", bpf->maplist[m].index); if (CRASHDEBUG(1)) sprintf(&buff[strlen(buff)], " (%lx)", (ulong)bpf->maplist[m].value); return buff; } } snprintf(buff, len, "0x%llx", (unsigned long long)full_imm); return buff; } static char *__func_get_name(const struct bpf_insn *insn, char *buff, size_t len) { long __BPF_FUNC_MAX_ID; char func_id_str[BUFSIZE]; ulong func_id_ptr; struct syment *sp; ulong offset; if (!enumerator_value("__BPF_FUNC_MAX_ID", &__BPF_FUNC_MAX_ID)) return buff; if (insn->src_reg != BPF_PSEUDO_CALL && insn->imm >= 0 && insn->imm < __BPF_FUNC_MAX_ID) { // return func_id_str[insn->imm]; if (!readmem(symbol_value("func_id_str") + (insn->imm * sizeof(void *)), KVADDR, &func_id_ptr, sizeof(void *), "func_id_str pointer", QUIET|RETURN_ON_ERROR)) error(FATAL, "cannot read func_id_str[]"); if (!read_string(func_id_ptr, func_id_str, BUFSIZE-1)) error(FATAL, "cannot read func_id_str[] string"); sprintf(buff, "%s", func_id_str); return buff; } if ((insn->src_reg != BPF_PSEUDO_CALL) && (sp = value_search(symbol_value("__bpf_call_base") + insn->imm, &offset)) && !offset) return(sp->name); if (insn->src_reg == BPF_PSEUDO_CALL) snprintf(buff, len, "%+d", insn->imm); return buff; } static void print_bpf_insn(struct bpf_insn *insn, int allow_ptr_leaks) { __u8 class = BPF_CLASS(insn->code); if (class == BPF_ALU || class == BPF_ALU64) { if (BPF_OP(insn->code) == BPF_END) { if (class == BPF_ALU64) fprintf(fp, "BUG_alu64_%02x\n", insn->code); else // print_bpf_end_insn(verbose, env, insn); fprintf(fp, "(%02x) r%d = %s%d r%d\n", insn->code, insn->dst_reg, BPF_SRC(insn->code) == BPF_TO_BE ? "be" : "le", insn->imm, insn->dst_reg); } else if (BPF_OP(insn->code) == BPF_NEG) { fprintf(fp, "(%02x) r%d = %s-r%d\n", insn->code, insn->dst_reg, class == BPF_ALU ? "(u32) " : "", insn->dst_reg); } else if (BPF_SRC(insn->code) == BPF_X) { fprintf(fp, "(%02x) %sr%d %s %sr%d\n", insn->code, class == BPF_ALU ? "(u32) " : "", insn->dst_reg, bpf_alu_string[BPF_OP(insn->code) >> 4], class == BPF_ALU ? "(u32) " : "", insn->src_reg); } else { fprintf(fp, "(%02x) %sr%d %s %s%d\n", insn->code, class == BPF_ALU ? "(u32) " : "", insn->dst_reg, bpf_alu_string[BPF_OP(insn->code) >> 4], class == BPF_ALU ? "(u32) " : "", insn->imm); } } else if (class == BPF_STX) { if (BPF_MODE(insn->code) == BPF_MEM) fprintf(fp, "(%02x) *(%s *)(r%d %+d) = r%d\n", insn->code, bpf_ldst_string[BPF_SIZE(insn->code) >> 3], insn->dst_reg, insn->off, insn->src_reg); else if (BPF_MODE(insn->code) == BPF_XADD) fprintf(fp, "(%02x) lock *(%s *)(r%d %+d) += r%d\n", insn->code, bpf_ldst_string[BPF_SIZE(insn->code) >> 3], insn->dst_reg, insn->off, insn->src_reg); else fprintf(fp, "BUG_%02x\n", insn->code); } else if (class == BPF_ST) { if (BPF_MODE(insn->code) != BPF_MEM) { fprintf(fp, "BUG_st_%02x\n", insn->code); return; } fprintf(fp, "(%02x) *(%s *)(r%d %+d) = %d\n", insn->code, bpf_ldst_string[BPF_SIZE(insn->code) >> 3], insn->dst_reg, insn->off, insn->imm); } else if (class == BPF_LDX) { if (BPF_MODE(insn->code) != BPF_MEM) { fprintf(fp, "BUG_ldx_%02x\n", insn->code); return; } fprintf(fp, "(%02x) r%d = *(%s *)(r%d %+d)\n", insn->code, insn->dst_reg, bpf_ldst_string[BPF_SIZE(insn->code) >> 3], insn->src_reg, insn->off); } else if (class == BPF_LD) { if (BPF_MODE(insn->code) == BPF_ABS) { fprintf(fp, "(%02x) r0 = *(%s *)skb[%d]\n", insn->code, bpf_ldst_string[BPF_SIZE(insn->code) >> 3], insn->imm); } else if (BPF_MODE(insn->code) == BPF_IND) { fprintf(fp, "(%02x) r0 = *(%s *)skb[r%d + %d]\n", insn->code, bpf_ldst_string[BPF_SIZE(insn->code) >> 3], insn->src_reg, insn->imm); } else if (BPF_MODE(insn->code) == BPF_IMM && BPF_SIZE(insn->code) == BPF_DW) { /* At this point, we already made sure that the second * part of the ldimm64 insn is accessible. */ uint64_t imm = ((uint64_t)(insn + 1)->imm << 32) | (u32)insn->imm; int map_ptr = insn->src_reg == BPF_PSEUDO_MAP_FD; char tmp[64]; if (map_ptr && !allow_ptr_leaks) imm = 0; fprintf(fp, "(%02x) r%d = %s\n", insn->code, insn->dst_reg, __func_imm_name(insn, imm, tmp, sizeof(tmp))); } else { fprintf(fp, "BUG_ld_%02x\n", insn->code); return; } } else if (class == BPF_JMP) { u8 opcode = BPF_OP(insn->code); if (opcode == BPF_CALL) { char tmp[64]; if (insn->src_reg == BPF_PSEUDO_CALL) { fprintf(fp, "(%02x) call pc%s\n", insn->code, __func_get_name(insn, tmp, sizeof(tmp))); } else { strcpy(tmp, "unknown"); fprintf(fp, "(%02x) call %s#%d\n", insn->code, __func_get_name(insn, tmp, sizeof(tmp)), insn->imm); } } else if (insn->code == (BPF_JMP | BPF_JA)) { fprintf(fp, "(%02x) goto pc%+d\n", insn->code, insn->off); } else if (insn->code == (BPF_JMP | BPF_EXIT)) { fprintf(fp, "(%02x) exit\n", insn->code); } else if (BPF_SRC(insn->code) == BPF_X) { fprintf(fp, "(%02x) if r%d %s r%d goto pc%+d\n", insn->code, insn->dst_reg, bpf_jmp_string[BPF_OP(insn->code) >> 4], insn->src_reg, insn->off); } else { fprintf(fp, "(%02x) if r%d %s 0x%x goto pc%+d\n", insn->code, insn->dst_reg, bpf_jmp_string[BPF_OP(insn->code) >> 4], insn->imm, insn->off); } } else { fprintf(fp, "(%02x) %s\n", insn->code, bpf_class_string[class]); } } static void print_boot_time(unsigned long long nsecs, char *buf, unsigned int size) { #ifdef CLOCK_BOOTTIME struct timespec real_time_ts, boot_time_ts; time_t wallclock_secs; struct tm load_tm; buf[--size] = '\0'; if (clock_gettime(CLOCK_REALTIME, &real_time_ts) || clock_gettime(CLOCK_BOOTTIME, &boot_time_ts)) { perror("Can't read clocks"); snprintf(buf, size, "%llu", nsecs / 1000000000); return; } wallclock_secs = (real_time_ts.tv_sec - boot_time_ts.tv_sec) + nsecs / 1000000000; if (!localtime_r(&wallclock_secs, &load_tm)) { snprintf(buf, size, "%llu", nsecs / 1000000000); return; } // strftime(buf, size, "%b %d/%H:%M", &load_tm); strftime(buf, size, "%a %b %d %H:%M:%S %Y", &load_tm); #else sprintf(buf, "(unknown)"); #endif } /* * Borrow the old (pre-radix_tree) IDR facility code used by * the ipcs command. */ static int do_old_idr(int cmd, ulong idr, struct list_pair *lp) { int i, max, cur, next_id, total = 0; ulong entry; switch (cmd) { case IDR_ORIG_INIT: ipcs_init(); break; case IDR_ORIG_COUNT: readmem(idr + OFFSET(idr_cur), KVADDR, &cur, sizeof(int), "idr.cur", FAULT_ON_ERROR); for (total = next_id = 0; next_id < cur; next_id++) { entry = idr_find(idr, next_id); if (entry == 0) continue; total++; } break; case IDR_ORIG_GATHER: max = lp[0].index; readmem(idr + OFFSET(idr_cur), KVADDR, &cur, sizeof(int), "idr.cur", FAULT_ON_ERROR); for (i = total = next_id = 0; next_id < cur; next_id++) { entry = idr_find(idr, next_id); if (entry == 0) continue; total++; lp[i].index = next_id; lp[i].value = (void *)entry; if (++i == max) break; } break; } return total; }