Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Eduard Zingerman | 3538 | 99.75% | 1 | 33.33% |
Abhishek Chauhan | 6 | 0.17% | 1 | 33.33% |
Jakub Kiciński | 3 | 0.08% | 1 | 33.33% |
Total | 3547 | 3 |
// SPDX-License-Identifier: GPL-2.0 #include <limits.h> #include <stdio.h> #include <string.h> #include <ctype.h> #include <regex.h> #include <test_progs.h> #include "bpf/btf.h" #include "bpf_util.h" #include "linux/filter.h" #include "disasm.h" #define MAX_PROG_TEXT_SZ (32 * 1024) /* The code in this file serves the sole purpose of executing test cases * specified in the test_cases array. Each test case specifies a program * type, context field offset, and disassembly patterns that correspond * to read and write instructions generated by * verifier.c:convert_ctx_access() for accessing that field. * * For each test case, up to three programs are created: * - One that uses BPF_LDX_MEM to read the context field. * - One that uses BPF_STX_MEM to write to the context field. * - One that uses BPF_ST_MEM to write to the context field. * * The disassembly of each program is then compared with the pattern * specified in the test case. */ struct test_case { char *name; enum bpf_prog_type prog_type; enum bpf_attach_type expected_attach_type; int field_offset; int field_sz; /* Program generated for BPF_ST_MEM uses value 42 by default, * this field allows to specify custom value. */ struct { bool use; int value; } st_value; /* Pattern for BPF_LDX_MEM(field_sz, dst, ctx, field_offset) */ char *read; /* Pattern for BPF_STX_MEM(field_sz, ctx, src, field_offset) and * BPF_ST_MEM (field_sz, ctx, src, field_offset) */ char *write; /* Pattern for BPF_ST_MEM(field_sz, ctx, src, field_offset), * takes priority over `write`. */ char *write_st; /* Pattern for BPF_STX_MEM (field_sz, ctx, src, field_offset), * takes priority over `write`. */ char *write_stx; }; #define N(_prog_type, type, field, name_extra...) \ .name = #_prog_type "." #field name_extra, \ .prog_type = BPF_PROG_TYPE_##_prog_type, \ .field_offset = offsetof(type, field), \ .field_sz = sizeof(typeof(((type *)NULL)->field)) static struct test_case test_cases[] = { /* Sign extension on s390 changes the pattern */ #if defined(__x86_64__) || defined(__aarch64__) { N(SCHED_CLS, struct __sk_buff, tstamp), .read = "r11 = *(u8 *)($ctx + sk_buff::__mono_tc_offset);" "if w11 & 0x4 goto pc+1;" "goto pc+4;" "if w11 & 0x3 goto pc+1;" "goto pc+2;" "$dst = 0;" "goto pc+1;" "$dst = *(u64 *)($ctx + sk_buff::tstamp);", .write = "r11 = *(u8 *)($ctx + sk_buff::__mono_tc_offset);" "if w11 & 0x4 goto pc+1;" "goto pc+2;" "w11 &= -4;" "*(u8 *)($ctx + sk_buff::__mono_tc_offset) = r11;" "*(u64 *)($ctx + sk_buff::tstamp) = $src;", }, #endif { N(SCHED_CLS, struct __sk_buff, priority), .read = "$dst = *(u32 *)($ctx + sk_buff::priority);", .write = "*(u32 *)($ctx + sk_buff::priority) = $src;", }, { N(SCHED_CLS, struct __sk_buff, mark), .read = "$dst = *(u32 *)($ctx + sk_buff::mark);", .write = "*(u32 *)($ctx + sk_buff::mark) = $src;", }, { N(SCHED_CLS, struct __sk_buff, cb[0]), .read = "$dst = *(u32 *)($ctx + $(sk_buff::cb + qdisc_skb_cb::data));", .write = "*(u32 *)($ctx + $(sk_buff::cb + qdisc_skb_cb::data)) = $src;", }, { N(SCHED_CLS, struct __sk_buff, tc_classid), .read = "$dst = *(u16 *)($ctx + $(sk_buff::cb + qdisc_skb_cb::tc_classid));", .write = "*(u16 *)($ctx + $(sk_buff::cb + qdisc_skb_cb::tc_classid)) = $src;", }, { N(SCHED_CLS, struct __sk_buff, tc_index), .read = "$dst = *(u16 *)($ctx + sk_buff::tc_index);", .write = "*(u16 *)($ctx + sk_buff::tc_index) = $src;", }, { N(SCHED_CLS, struct __sk_buff, queue_mapping), .read = "$dst = *(u16 *)($ctx + sk_buff::queue_mapping);", .write_stx = "if $src >= 0xffff goto pc+1;" "*(u16 *)($ctx + sk_buff::queue_mapping) = $src;", .write_st = "*(u16 *)($ctx + sk_buff::queue_mapping) = $src;", }, { /* This is a corner case in filter.c:bpf_convert_ctx_access() */ N(SCHED_CLS, struct __sk_buff, queue_mapping, ".ushrt_max"), .st_value = { true, USHRT_MAX }, .write_st = "goto pc+0;", }, { N(CGROUP_SOCK, struct bpf_sock, bound_dev_if), .read = "$dst = *(u32 *)($ctx + sock_common::skc_bound_dev_if);", .write = "*(u32 *)($ctx + sock_common::skc_bound_dev_if) = $src;", }, { N(CGROUP_SOCK, struct bpf_sock, mark), .read = "$dst = *(u32 *)($ctx + sock::sk_mark);", .write = "*(u32 *)($ctx + sock::sk_mark) = $src;", }, { N(CGROUP_SOCK, struct bpf_sock, priority), .read = "$dst = *(u32 *)($ctx + sock::sk_priority);", .write = "*(u32 *)($ctx + sock::sk_priority) = $src;", }, { N(SOCK_OPS, struct bpf_sock_ops, replylong[0]), .read = "$dst = *(u32 *)($ctx + bpf_sock_ops_kern::replylong);", .write = "*(u32 *)($ctx + bpf_sock_ops_kern::replylong) = $src;", }, { N(CGROUP_SYSCTL, struct bpf_sysctl, file_pos), #if __BYTE_ORDER == __LITTLE_ENDIAN .read = "$dst = *(u64 *)($ctx + bpf_sysctl_kern::ppos);" "$dst = *(u32 *)($dst +0);", .write = "*(u64 *)($ctx + bpf_sysctl_kern::tmp_reg) = r9;" "r9 = *(u64 *)($ctx + bpf_sysctl_kern::ppos);" "*(u32 *)(r9 +0) = $src;" "r9 = *(u64 *)($ctx + bpf_sysctl_kern::tmp_reg);", #else .read = "$dst = *(u64 *)($ctx + bpf_sysctl_kern::ppos);" "$dst = *(u32 *)($dst +4);", .write = "*(u64 *)($ctx + bpf_sysctl_kern::tmp_reg) = r9;" "r9 = *(u64 *)($ctx + bpf_sysctl_kern::ppos);" "*(u32 *)(r9 +4) = $src;" "r9 = *(u64 *)($ctx + bpf_sysctl_kern::tmp_reg);", #endif }, { N(CGROUP_SOCKOPT, struct bpf_sockopt, sk), .read = "$dst = *(u64 *)($ctx + bpf_sockopt_kern::sk);", .expected_attach_type = BPF_CGROUP_GETSOCKOPT, }, { N(CGROUP_SOCKOPT, struct bpf_sockopt, level), .read = "$dst = *(u32 *)($ctx + bpf_sockopt_kern::level);", .write = "*(u32 *)($ctx + bpf_sockopt_kern::level) = $src;", .expected_attach_type = BPF_CGROUP_SETSOCKOPT, }, { N(CGROUP_SOCKOPT, struct bpf_sockopt, optname), .read = "$dst = *(u32 *)($ctx + bpf_sockopt_kern::optname);", .write = "*(u32 *)($ctx + bpf_sockopt_kern::optname) = $src;", .expected_attach_type = BPF_CGROUP_SETSOCKOPT, }, { N(CGROUP_SOCKOPT, struct bpf_sockopt, optlen), .read = "$dst = *(u32 *)($ctx + bpf_sockopt_kern::optlen);", .write = "*(u32 *)($ctx + bpf_sockopt_kern::optlen) = $src;", .expected_attach_type = BPF_CGROUP_SETSOCKOPT, }, { N(CGROUP_SOCKOPT, struct bpf_sockopt, retval), .read = "$dst = *(u64 *)($ctx + bpf_sockopt_kern::current_task);" "$dst = *(u64 *)($dst + task_struct::bpf_ctx);" "$dst = *(u32 *)($dst + bpf_cg_run_ctx::retval);", .write = "*(u64 *)($ctx + bpf_sockopt_kern::tmp_reg) = r9;" "r9 = *(u64 *)($ctx + bpf_sockopt_kern::current_task);" "r9 = *(u64 *)(r9 + task_struct::bpf_ctx);" "*(u32 *)(r9 + bpf_cg_run_ctx::retval) = $src;" "r9 = *(u64 *)($ctx + bpf_sockopt_kern::tmp_reg);", .expected_attach_type = BPF_CGROUP_GETSOCKOPT, }, { N(CGROUP_SOCKOPT, struct bpf_sockopt, optval), .read = "$dst = *(u64 *)($ctx + bpf_sockopt_kern::optval);", .expected_attach_type = BPF_CGROUP_GETSOCKOPT, }, { N(CGROUP_SOCKOPT, struct bpf_sockopt, optval_end), .read = "$dst = *(u64 *)($ctx + bpf_sockopt_kern::optval_end);", .expected_attach_type = BPF_CGROUP_GETSOCKOPT, }, }; #undef N static regex_t *ident_regex; static regex_t *field_regex; static char *skip_space(char *str) { while (*str && isspace(*str)) ++str; return str; } static char *skip_space_and_semi(char *str) { while (*str && (isspace(*str) || *str == ';')) ++str; return str; } static char *match_str(char *str, char *prefix) { while (*str && *prefix && *str == *prefix) { ++str; ++prefix; } if (*prefix) return NULL; return str; } static char *match_number(char *str, int num) { char *next; int snum = strtol(str, &next, 10); if (next - str == 0 || num != snum) return NULL; return next; } static int find_field_offset_aux(struct btf *btf, int btf_id, char *field_name, int off) { const struct btf_type *type = btf__type_by_id(btf, btf_id); const struct btf_member *m; __u16 mnum; int i; if (!type) { PRINT_FAIL("Can't find btf_type for id %d\n", btf_id); return -1; } if (!btf_is_struct(type) && !btf_is_union(type)) { PRINT_FAIL("BTF id %d is not struct or union\n", btf_id); return -1; } m = btf_members(type); mnum = btf_vlen(type); for (i = 0; i < mnum; ++i, ++m) { const char *mname = btf__name_by_offset(btf, m->name_off); if (strcmp(mname, "") == 0) { int msize = find_field_offset_aux(btf, m->type, field_name, off + m->offset); if (msize >= 0) return msize; } if (strcmp(mname, field_name)) continue; return (off + m->offset) / 8; } return -1; } static int find_field_offset(struct btf *btf, char *pattern, regmatch_t *matches) { int type_sz = matches[1].rm_eo - matches[1].rm_so; int field_sz = matches[2].rm_eo - matches[2].rm_so; char *type = pattern + matches[1].rm_so; char *field = pattern + matches[2].rm_so; char field_str[128] = {}; char type_str[128] = {}; int btf_id, field_offset; if (type_sz >= sizeof(type_str)) { PRINT_FAIL("Malformed pattern: type ident is too long: %d\n", type_sz); return -1; } if (field_sz >= sizeof(field_str)) { PRINT_FAIL("Malformed pattern: field ident is too long: %d\n", field_sz); return -1; } strncpy(type_str, type, type_sz); strncpy(field_str, field, field_sz); btf_id = btf__find_by_name(btf, type_str); if (btf_id < 0) { PRINT_FAIL("No BTF info for type %s\n", type_str); return -1; } field_offset = find_field_offset_aux(btf, btf_id, field_str, 0); if (field_offset < 0) { PRINT_FAIL("No BTF info for field %s::%s\n", type_str, field_str); return -1; } return field_offset; } static regex_t *compile_regex(char *pat) { regex_t *re; int err; re = malloc(sizeof(regex_t)); if (!re) { PRINT_FAIL("Can't alloc regex\n"); return NULL; } err = regcomp(re, pat, REG_EXTENDED); if (err) { char errbuf[512]; regerror(err, re, errbuf, sizeof(errbuf)); PRINT_FAIL("Can't compile regex: %s\n", errbuf); free(re); return NULL; } return re; } static void free_regex(regex_t *re) { if (!re) return; regfree(re); free(re); } static u32 max_line_len(char *str) { u32 max_line = 0; char *next = str; while (next) { next = strchr(str, '\n'); if (next) { max_line = max_t(u32, max_line, (next - str)); str = next + 1; } else { max_line = max_t(u32, max_line, strlen(str)); } } return min(max_line, 60u); } /* Print strings `pattern_origin` and `text_origin` side by side, * assume `pattern_pos` and `text_pos` designate location within * corresponding origin string where match diverges. * The output should look like: * * Can't match disassembly(left) with pattern(right): * r2 = *(u64 *)(r1 +0) ; $dst = *(u64 *)($ctx + bpf_sockopt_kern::sk1) * ^ ^ * r0 = 0 ; * exit ; */ static void print_match_error(FILE *out, char *pattern_origin, char *text_origin, char *pattern_pos, char *text_pos) { char *pattern = pattern_origin; char *text = text_origin; int middle = max_line_len(text) + 2; fprintf(out, "Can't match disassembly(left) with pattern(right):\n"); while (*pattern || *text) { int column = 0; int mark1 = -1; int mark2 = -1; /* Print one line from text */ while (*text && *text != '\n') { if (text == text_pos) mark1 = column; fputc(*text, out); ++text; ++column; } if (text == text_pos) mark1 = column; /* Pad to the middle */ while (column < middle) { fputc(' ', out); ++column; } fputs("; ", out); column += 3; /* Print one line from pattern, pattern lines are terminated by ';' */ while (*pattern && *pattern != ';') { if (pattern == pattern_pos) mark2 = column; fputc(*pattern, out); ++pattern; ++column; } if (pattern == pattern_pos) mark2 = column; fputc('\n', out); if (*pattern) ++pattern; if (*text) ++text; /* If pattern and text diverge at this line, print an * additional line with '^' marks, highlighting * positions where match fails. */ if (mark1 > 0 || mark2 > 0) { for (column = 0; column <= max(mark1, mark2); ++column) { if (column == mark1 || column == mark2) fputc('^', out); else fputc(' ', out); } fputc('\n', out); } } } /* Test if `text` matches `pattern`. Pattern consists of the following elements: * * - Field offset references: * * <type>::<field> * * When such reference is encountered BTF is used to compute numerical * value for the offset of <field> in <type>. The `text` is expected to * contain matching numerical value. * * - Field groups: * * $(<type>::<field> [+ <type>::<field>]*) * * Allows to specify an offset that is a sum of multiple field offsets. * The `text` is expected to contain matching numerical value. * * - Variable references, e.g. `$src`, `$dst`, `$ctx`. * These are substitutions specified in `reg_map` array. * If a substring of pattern is equal to `reg_map[i][0]` the `text` is * expected to contain `reg_map[i][1]` in the matching position. * * - Whitespace is ignored, ';' counts as whitespace for `pattern`. * * - Any other characters, `pattern` and `text` should match one-to-one. * * Example of a pattern: * * __________ fields group ________________ * ' ' * *(u16 *)($ctx + $(sk_buff::cb + qdisc_skb_cb::tc_classid)) = $src; * ^^^^ '______________________' * variable reference field offset reference */ static bool match_pattern(struct btf *btf, char *pattern, char *text, char *reg_map[][2]) { char *pattern_origin = pattern; char *text_origin = text; regmatch_t matches[3]; _continue: while (*pattern) { if (!*text) goto err; /* Skip whitespace */ if (isspace(*pattern) || *pattern == ';') { if (!isspace(*text) && text != text_origin && isalnum(text[-1])) goto err; pattern = skip_space_and_semi(pattern); text = skip_space(text); continue; } /* Check for variable references */ for (int i = 0; reg_map[i][0]; ++i) { char *pattern_next, *text_next; pattern_next = match_str(pattern, reg_map[i][0]); if (!pattern_next) continue; text_next = match_str(text, reg_map[i][1]); if (!text_next) goto err; pattern = pattern_next; text = text_next; goto _continue; } /* Match field group: * $(sk_buff::cb + qdisc_skb_cb::tc_classid) */ if (strncmp(pattern, "$(", 2) == 0) { char *group_start = pattern, *text_next; int acc_offset = 0; pattern += 2; for (;;) { int field_offset; pattern = skip_space(pattern); if (!*pattern) { PRINT_FAIL("Unexpected end of pattern\n"); goto err; } if (*pattern == ')') { ++pattern; break; } if (*pattern == '+') { ++pattern; continue; } printf("pattern: %s\n", pattern); if (regexec(field_regex, pattern, 3, matches, 0) != 0) { PRINT_FAIL("Field reference expected\n"); goto err; } field_offset = find_field_offset(btf, pattern, matches); if (field_offset < 0) goto err; pattern += matches[0].rm_eo; acc_offset += field_offset; } text_next = match_number(text, acc_offset); if (!text_next) { PRINT_FAIL("No match for group offset %.*s (%d)\n", (int)(pattern - group_start), group_start, acc_offset); goto err; } text = text_next; } /* Match field reference: * sk_buff::cb */ if (regexec(field_regex, pattern, 3, matches, 0) == 0) { int field_offset; char *text_next; field_offset = find_field_offset(btf, pattern, matches); if (field_offset < 0) goto err; text_next = match_number(text, field_offset); if (!text_next) { PRINT_FAIL("No match for field offset %.*s (%d)\n", (int)matches[0].rm_eo, pattern, field_offset); goto err; } pattern += matches[0].rm_eo; text = text_next; continue; } /* If pattern points to identifier not followed by '::' * skip the identifier to avoid n^2 application of the * field reference rule. */ if (regexec(ident_regex, pattern, 1, matches, 0) == 0) { if (strncmp(pattern, text, matches[0].rm_eo) != 0) goto err; pattern += matches[0].rm_eo; text += matches[0].rm_eo; continue; } /* Match literally */ if (*pattern != *text) goto err; ++pattern; ++text; } return true; err: test__fail(); print_match_error(stdout, pattern_origin, text_origin, pattern, text); return false; } static void print_insn(void *private_data, const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf((FILE *)private_data, fmt, args); va_end(args); } /* Disassemble instructions to a stream */ static void print_xlated(FILE *out, struct bpf_insn *insn, __u32 len) { const struct bpf_insn_cbs cbs = { .cb_print = print_insn, .cb_call = NULL, .cb_imm = NULL, .private_data = out, }; bool double_insn = false; int i; for (i = 0; i < len; i++) { if (double_insn) { double_insn = false; continue; } double_insn = insn[i].code == (BPF_LD | BPF_IMM | BPF_DW); print_bpf_insn(&cbs, insn + i, true); } } /* We share code with kernel BPF disassembler, it adds '(FF) ' prefix * for each instruction (FF stands for instruction `code` byte). * This function removes the prefix inplace for each line in `str`. */ static void remove_insn_prefix(char *str, int size) { const int prefix_size = 5; int write_pos = 0, read_pos = prefix_size; int len = strlen(str); char c; size = min(size, len); while (read_pos < size) { c = str[read_pos++]; if (c == 0) break; str[write_pos++] = c; if (c == '\n') read_pos += prefix_size; } str[write_pos] = 0; } struct prog_info { char *prog_kind; enum bpf_prog_type prog_type; enum bpf_attach_type expected_attach_type; struct bpf_insn *prog; u32 prog_len; }; static void match_program(struct btf *btf, struct prog_info *pinfo, char *pattern, char *reg_map[][2], bool skip_first_insn) { struct bpf_insn *buf = NULL; int err = 0, prog_fd = 0; FILE *prog_out = NULL; char *text = NULL; __u32 cnt = 0; text = calloc(MAX_PROG_TEXT_SZ, 1); if (!text) { PRINT_FAIL("Can't allocate %d bytes\n", MAX_PROG_TEXT_SZ); goto out; } // TODO: log level LIBBPF_OPTS(bpf_prog_load_opts, opts); opts.log_buf = text; opts.log_size = MAX_PROG_TEXT_SZ; opts.log_level = 1 | 2 | 4; opts.expected_attach_type = pinfo->expected_attach_type; prog_fd = bpf_prog_load(pinfo->prog_type, NULL, "GPL", pinfo->prog, pinfo->prog_len, &opts); if (prog_fd < 0) { PRINT_FAIL("Can't load program, errno %d (%s), verifier log:\n%s\n", errno, strerror(errno), text); goto out; } memset(text, 0, MAX_PROG_TEXT_SZ); err = get_xlated_program(prog_fd, &buf, &cnt); if (err) { PRINT_FAIL("Can't load back BPF program\n"); goto out; } prog_out = fmemopen(text, MAX_PROG_TEXT_SZ - 1, "w"); if (!prog_out) { PRINT_FAIL("Can't open memory stream\n"); goto out; } if (skip_first_insn) print_xlated(prog_out, buf + 1, cnt - 1); else print_xlated(prog_out, buf, cnt); fclose(prog_out); remove_insn_prefix(text, MAX_PROG_TEXT_SZ); ASSERT_TRUE(match_pattern(btf, pattern, text, reg_map), pinfo->prog_kind); out: if (prog_fd) close(prog_fd); free(buf); free(text); } static void run_one_testcase(struct btf *btf, struct test_case *test) { struct prog_info pinfo = {}; int bpf_sz; if (!test__start_subtest(test->name)) return; switch (test->field_sz) { case 8: bpf_sz = BPF_DW; break; case 4: bpf_sz = BPF_W; break; case 2: bpf_sz = BPF_H; break; case 1: bpf_sz = BPF_B; break; default: PRINT_FAIL("Unexpected field size: %d, want 8,4,2 or 1\n", test->field_sz); return; } pinfo.prog_type = test->prog_type; pinfo.expected_attach_type = test->expected_attach_type; if (test->read) { struct bpf_insn ldx_prog[] = { BPF_LDX_MEM(bpf_sz, BPF_REG_2, BPF_REG_1, test->field_offset), BPF_MOV64_IMM(BPF_REG_0, 0), BPF_EXIT_INSN(), }; char *reg_map[][2] = { { "$ctx", "r1" }, { "$dst", "r2" }, {} }; pinfo.prog_kind = "LDX"; pinfo.prog = ldx_prog; pinfo.prog_len = ARRAY_SIZE(ldx_prog); match_program(btf, &pinfo, test->read, reg_map, false); } if (test->write || test->write_st || test->write_stx) { struct bpf_insn stx_prog[] = { BPF_MOV64_IMM(BPF_REG_2, 0), BPF_STX_MEM(bpf_sz, BPF_REG_1, BPF_REG_2, test->field_offset), BPF_MOV64_IMM(BPF_REG_0, 0), BPF_EXIT_INSN(), }; char *stx_reg_map[][2] = { { "$ctx", "r1" }, { "$src", "r2" }, {} }; struct bpf_insn st_prog[] = { BPF_ST_MEM(bpf_sz, BPF_REG_1, test->field_offset, test->st_value.use ? test->st_value.value : 42), BPF_MOV64_IMM(BPF_REG_0, 0), BPF_EXIT_INSN(), }; char *st_reg_map[][2] = { { "$ctx", "r1" }, { "$src", "42" }, {} }; if (test->write || test->write_stx) { char *pattern = test->write_stx ? test->write_stx : test->write; pinfo.prog_kind = "STX"; pinfo.prog = stx_prog; pinfo.prog_len = ARRAY_SIZE(stx_prog); match_program(btf, &pinfo, pattern, stx_reg_map, true); } if (test->write || test->write_st) { char *pattern = test->write_st ? test->write_st : test->write; pinfo.prog_kind = "ST"; pinfo.prog = st_prog; pinfo.prog_len = ARRAY_SIZE(st_prog); match_program(btf, &pinfo, pattern, st_reg_map, false); } } test__end_subtest(); } void test_ctx_rewrite(void) { struct btf *btf; int i; field_regex = compile_regex("^([[:alpha:]_][[:alnum:]_]+)::([[:alpha:]_][[:alnum:]_]+)"); ident_regex = compile_regex("^[[:alpha:]_][[:alnum:]_]+"); if (!field_regex || !ident_regex) return; btf = btf__load_vmlinux_btf(); if (!btf) { PRINT_FAIL("Can't load vmlinux BTF, errno %d (%s)\n", errno, strerror(errno)); goto out; } for (i = 0; i < ARRAY_SIZE(test_cases); ++i) run_one_testcase(btf, &test_cases[i]); out: btf__free(btf); free_regex(field_regex); free_regex(ident_regex); }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1