mirror of https://github.com/crash-utility/crash
1412 lines
38 KiB
C
1412 lines
38 KiB
C
/* 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_prog_aux_name, "bpf_prog_aux", "name");
|
|
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");
|
|
|
|
/* Linux 5.3 */
|
|
MEMBER_OFFSET_INIT(bpf_map_memory, "bpf_map", "memory");
|
|
if (VALID_MEMBER(bpf_map_memory)) {
|
|
MEMBER_OFFSET_INIT(bpf_map_memory_pages, "bpf_map_memory", "pages");
|
|
MEMBER_OFFSET_INIT(bpf_map_memory_user, "bpf_map_memory", "user");
|
|
}
|
|
|
|
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, " NAME: ");
|
|
if (VALID_MEMBER(bpf_prog_aux_name)) {
|
|
BCOPY(&bpf->bpf_prog_aux_buf[OFFSET(bpf_prog_aux_name)], buf1, 16);
|
|
buf1[16] = NULLCHAR;
|
|
if (strlen(buf1))
|
|
fprintf(fp, "\"%s\"", buf1);
|
|
else
|
|
fprintf(fp, "(unused)");
|
|
} else
|
|
fprintf(fp, "(unknown)");
|
|
|
|
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_memory) && VALID_MEMBER(bpf_map_memory_pages)) {
|
|
map_pages = UINT(bpf->bpf_map_buf + OFFSET(bpf_map_memory)
|
|
+ OFFSET(bpf_map_memory_pages));
|
|
fprintf(fp, "%d\n", map_pages * PAGESIZE());
|
|
} else 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_memory) && VALID_MEMBER(bpf_map_memory_user))
|
|
user = ULONG(bpf->bpf_map_buf + OFFSET(bpf_map_memory)
|
|
+ OFFSET(bpf_map_memory_user));
|
|
else if (VALID_MEMBER(bpf_map_user))
|
|
user = ULONG(bpf->bpf_map_buf + OFFSET(bpf_map_user));
|
|
else
|
|
user = 0;
|
|
|
|
if (user && VALID_MEMBER(user_struct_uid)) {
|
|
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 <linux/bpf_common.h>
|
|
|
|
/*
|
|
* 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;
|
|
}
|