Commit d54d06a4 authored by Alexei Starovoitov's avatar Alexei Starovoitov

Merge branch 'Teach libbpf to "fix up" BPF verifier log'

Andrii Nakryiko says:

====================

This patch set teaches libbpf to enhance BPF verifier log with human-readable
and relevant information about failed CO-RE relocation. Patch #9 is the main
one with the new logic. See relevant commit messages for some more details.

All the other patches are either fixing various bugs detected
while working on this feature, most prominently a bug with libbpf not handling
CO-RE relocations for SEC("?...") programs, or are refactoring libbpf
internals to allow for easier reuse of CO-RE relo lookup and formatting logic.
====================
Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents c317ab71 ea4128eb
...@@ -2626,6 +2626,7 @@ static int btf_ext_setup_info(struct btf_ext *btf_ext, ...@@ -2626,6 +2626,7 @@ static int btf_ext_setup_info(struct btf_ext *btf_ext,
const struct btf_ext_info_sec *sinfo; const struct btf_ext_info_sec *sinfo;
struct btf_ext_info *ext_info; struct btf_ext_info *ext_info;
__u32 info_left, record_size; __u32 info_left, record_size;
size_t sec_cnt = 0;
/* The start of the info sec (including the __u32 record_size). */ /* The start of the info sec (including the __u32 record_size). */
void *info; void *info;
...@@ -2689,8 +2690,7 @@ static int btf_ext_setup_info(struct btf_ext *btf_ext, ...@@ -2689,8 +2690,7 @@ static int btf_ext_setup_info(struct btf_ext *btf_ext,
return -EINVAL; return -EINVAL;
} }
total_record_size = sec_hdrlen + total_record_size = sec_hdrlen + (__u64)num_records * record_size;
(__u64)num_records * record_size;
if (info_left < total_record_size) { if (info_left < total_record_size) {
pr_debug("%s section has incorrect num_records in .BTF.ext\n", pr_debug("%s section has incorrect num_records in .BTF.ext\n",
ext_sec->desc); ext_sec->desc);
...@@ -2699,12 +2699,14 @@ static int btf_ext_setup_info(struct btf_ext *btf_ext, ...@@ -2699,12 +2699,14 @@ static int btf_ext_setup_info(struct btf_ext *btf_ext,
info_left -= total_record_size; info_left -= total_record_size;
sinfo = (void *)sinfo + total_record_size; sinfo = (void *)sinfo + total_record_size;
sec_cnt++;
} }
ext_info = ext_sec->ext_info; ext_info = ext_sec->ext_info;
ext_info->len = ext_sec->len - sizeof(__u32); ext_info->len = ext_sec->len - sizeof(__u32);
ext_info->rec_size = record_size; ext_info->rec_size = record_size;
ext_info->info = info + sizeof(__u32); ext_info->info = info + sizeof(__u32);
ext_info->sec_cnt = sec_cnt;
return 0; return 0;
} }
...@@ -2788,6 +2790,9 @@ void btf_ext__free(struct btf_ext *btf_ext) ...@@ -2788,6 +2790,9 @@ void btf_ext__free(struct btf_ext *btf_ext)
{ {
if (IS_ERR_OR_NULL(btf_ext)) if (IS_ERR_OR_NULL(btf_ext))
return; return;
free(btf_ext->func_info.sec_idxs);
free(btf_ext->line_info.sec_idxs);
free(btf_ext->core_relo_info.sec_idxs);
free(btf_ext->data); free(btf_ext->data);
free(btf_ext); free(btf_ext);
} }
......
...@@ -2765,6 +2765,9 @@ static int bpf_object__init_btf(struct bpf_object *obj, ...@@ -2765,6 +2765,9 @@ static int bpf_object__init_btf(struct bpf_object *obj,
btf__set_pointer_size(obj->btf, 8); btf__set_pointer_size(obj->btf, 8);
} }
if (btf_ext_data) { if (btf_ext_data) {
struct btf_ext_info *ext_segs[3];
int seg_num, sec_num;
if (!obj->btf) { if (!obj->btf) {
pr_debug("Ignore ELF section %s because its depending ELF section %s is not found.\n", pr_debug("Ignore ELF section %s because its depending ELF section %s is not found.\n",
BTF_EXT_ELF_SEC, BTF_ELF_SEC); BTF_EXT_ELF_SEC, BTF_ELF_SEC);
...@@ -2778,6 +2781,43 @@ static int bpf_object__init_btf(struct bpf_object *obj, ...@@ -2778,6 +2781,43 @@ static int bpf_object__init_btf(struct bpf_object *obj,
obj->btf_ext = NULL; obj->btf_ext = NULL;
goto out; goto out;
} }
/* setup .BTF.ext to ELF section mapping */
ext_segs[0] = &obj->btf_ext->func_info;
ext_segs[1] = &obj->btf_ext->line_info;
ext_segs[2] = &obj->btf_ext->core_relo_info;
for (seg_num = 0; seg_num < ARRAY_SIZE(ext_segs); seg_num++) {
struct btf_ext_info *seg = ext_segs[seg_num];
const struct btf_ext_info_sec *sec;
const char *sec_name;
Elf_Scn *scn;
if (seg->sec_cnt == 0)
continue;
seg->sec_idxs = calloc(seg->sec_cnt, sizeof(*seg->sec_idxs));
if (!seg->sec_idxs) {
err = -ENOMEM;
goto out;
}
sec_num = 0;
for_each_btf_ext_sec(seg, sec) {
/* preventively increment index to avoid doing
* this before every continue below
*/
sec_num++;
sec_name = btf__name_by_offset(obj->btf, sec->sec_name_off);
if (str_is_empty(sec_name))
continue;
scn = elf_sec_by_name(obj, sec_name);
if (!scn)
continue;
seg->sec_idxs[sec_num - 1] = elf_ndxscn(scn);
}
}
} }
out: out:
if (err && libbpf_needs_btf(obj)) { if (err && libbpf_needs_btf(obj)) {
...@@ -5586,6 +5626,22 @@ static int record_relo_core(struct bpf_program *prog, ...@@ -5586,6 +5626,22 @@ static int record_relo_core(struct bpf_program *prog,
return 0; return 0;
} }
static const struct bpf_core_relo *find_relo_core(struct bpf_program *prog, int insn_idx)
{
struct reloc_desc *relo;
int i;
for (i = 0; i < prog->nr_reloc; i++) {
relo = &prog->reloc_desc[i];
if (relo->type != RELO_CORE || relo->insn_idx != insn_idx)
continue;
return relo->core_relo;
}
return NULL;
}
static int bpf_core_resolve_relo(struct bpf_program *prog, static int bpf_core_resolve_relo(struct bpf_program *prog,
const struct bpf_core_relo *relo, const struct bpf_core_relo *relo,
int relo_idx, int relo_idx,
...@@ -5642,7 +5698,7 @@ bpf_object__relocate_core(struct bpf_object *obj, const char *targ_btf_path) ...@@ -5642,7 +5698,7 @@ bpf_object__relocate_core(struct bpf_object *obj, const char *targ_btf_path)
struct bpf_program *prog; struct bpf_program *prog;
struct bpf_insn *insn; struct bpf_insn *insn;
const char *sec_name; const char *sec_name;
int i, err = 0, insn_idx, sec_idx; int i, err = 0, insn_idx, sec_idx, sec_num;
if (obj->btf_ext->core_relo_info.len == 0) if (obj->btf_ext->core_relo_info.len == 0)
return 0; return 0;
...@@ -5663,32 +5719,18 @@ bpf_object__relocate_core(struct bpf_object *obj, const char *targ_btf_path) ...@@ -5663,32 +5719,18 @@ bpf_object__relocate_core(struct bpf_object *obj, const char *targ_btf_path)
} }
seg = &obj->btf_ext->core_relo_info; seg = &obj->btf_ext->core_relo_info;
sec_num = 0;
for_each_btf_ext_sec(seg, sec) { for_each_btf_ext_sec(seg, sec) {
sec_idx = seg->sec_idxs[sec_num];
sec_num++;
sec_name = btf__name_by_offset(obj->btf, sec->sec_name_off); sec_name = btf__name_by_offset(obj->btf, sec->sec_name_off);
if (str_is_empty(sec_name)) { if (str_is_empty(sec_name)) {
err = -EINVAL; err = -EINVAL;
goto out; goto out;
} }
/* bpf_object's ELF is gone by now so it's not easy to find
* section index by section name, but we can find *any*
* bpf_program within desired section name and use it's
* prog->sec_idx to do a proper search by section index and
* instruction offset
*/
prog = NULL;
for (i = 0; i < obj->nr_programs; i++) {
prog = &obj->programs[i];
if (strcmp(prog->sec_name, sec_name) == 0)
break;
}
if (!prog) {
pr_warn("sec '%s': failed to find a BPF program\n", sec_name);
return -ENOENT;
}
sec_idx = prog->sec_idx;
pr_debug("sec '%s': found %d CO-RE relocations\n", pr_debug("sec '%s': found %d CO-RE relocations\n", sec_name, sec->num_info);
sec_name, sec->num_info);
for_each_btf_ext_rec(seg, sec, i, rec) { for_each_btf_ext_rec(seg, sec, i, rec) {
if (rec->insn_off % BPF_INSN_SZ) if (rec->insn_off % BPF_INSN_SZ)
...@@ -5723,16 +5765,16 @@ bpf_object__relocate_core(struct bpf_object *obj, const char *targ_btf_path) ...@@ -5723,16 +5765,16 @@ bpf_object__relocate_core(struct bpf_object *obj, const char *targ_btf_path)
return -EINVAL; return -EINVAL;
insn = &prog->insns[insn_idx]; insn = &prog->insns[insn_idx];
if (prog->obj->gen_loader) { err = record_relo_core(prog, rec, insn_idx);
err = record_relo_core(prog, rec, insn_idx); if (err) {
if (err) { pr_warn("prog '%s': relo #%d: failed to record relocation: %d\n",
pr_warn("prog '%s': relo #%d: failed to record relocation: %d\n", prog->name, i, err);
prog->name, i, err); goto out;
goto out;
}
continue;
} }
if (prog->obj->gen_loader)
continue;
err = bpf_core_resolve_relo(prog, rec, i, obj->btf, cand_cache, &targ_res); err = bpf_core_resolve_relo(prog, rec, i, obj->btf, cand_cache, &targ_res);
if (err) { if (err) {
pr_warn("prog '%s': relo #%d: failed to relocate: %d\n", pr_warn("prog '%s': relo #%d: failed to relocate: %d\n",
...@@ -5872,14 +5914,13 @@ static int adjust_prog_btf_ext_info(const struct bpf_object *obj, ...@@ -5872,14 +5914,13 @@ static int adjust_prog_btf_ext_info(const struct bpf_object *obj,
void *rec, *rec_end, *new_prog_info; void *rec, *rec_end, *new_prog_info;
const struct btf_ext_info_sec *sec; const struct btf_ext_info_sec *sec;
size_t old_sz, new_sz; size_t old_sz, new_sz;
const char *sec_name; int i, sec_num, sec_idx, off_adj;
int i, off_adj;
sec_num = 0;
for_each_btf_ext_sec(ext_info, sec) { for_each_btf_ext_sec(ext_info, sec) {
sec_name = btf__name_by_offset(obj->btf, sec->sec_name_off); sec_idx = ext_info->sec_idxs[sec_num];
if (!sec_name) sec_num++;
return -EINVAL; if (prog->sec_idx != sec_idx)
if (strcmp(sec_name, prog->sec_name) != 0)
continue; continue;
for_each_btf_ext_rec(ext_info, sec, i, rec) { for_each_btf_ext_rec(ext_info, sec, i, rec) {
...@@ -6274,7 +6315,6 @@ bpf_object__relocate_calls(struct bpf_object *obj, struct bpf_program *prog) ...@@ -6274,7 +6315,6 @@ bpf_object__relocate_calls(struct bpf_object *obj, struct bpf_program *prog)
if (err) if (err)
return err; return err;
return 0; return 0;
} }
...@@ -6335,8 +6375,7 @@ bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_path) ...@@ -6335,8 +6375,7 @@ bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_path)
err); err);
return err; return err;
} }
if (obj->gen_loader) bpf_object__sort_relos(obj);
bpf_object__sort_relos(obj);
} }
/* Before relocating calls pre-process relocations and mark /* Before relocating calls pre-process relocations and mark
...@@ -6396,8 +6435,7 @@ bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_path) ...@@ -6396,8 +6435,7 @@ bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_path)
return err; return err;
} }
} }
if (!obj->gen_loader)
bpf_object__free_relocs(obj);
return 0; return 0;
} }
...@@ -6674,6 +6712,8 @@ static int libbpf_prepare_prog_load(struct bpf_program *prog, ...@@ -6674,6 +6712,8 @@ static int libbpf_prepare_prog_load(struct bpf_program *prog,
return 0; return 0;
} }
static void fixup_verifier_log(struct bpf_program *prog, char *buf, size_t buf_sz);
static int bpf_object_load_prog_instance(struct bpf_object *obj, struct bpf_program *prog, static int bpf_object_load_prog_instance(struct bpf_object *obj, struct bpf_program *prog,
struct bpf_insn *insns, int insns_cnt, struct bpf_insn *insns, int insns_cnt,
const char *license, __u32 kern_version, const char *license, __u32 kern_version,
...@@ -6820,6 +6860,10 @@ static int bpf_object_load_prog_instance(struct bpf_object *obj, struct bpf_prog ...@@ -6820,6 +6860,10 @@ static int bpf_object_load_prog_instance(struct bpf_object *obj, struct bpf_prog
goto retry_load; goto retry_load;
ret = -errno; ret = -errno;
/* post-process verifier log to improve error descriptions */
fixup_verifier_log(prog, log_buf, log_buf_size);
cp = libbpf_strerror_r(errno, errmsg, sizeof(errmsg)); cp = libbpf_strerror_r(errno, errmsg, sizeof(errmsg));
pr_warn("prog '%s': BPF program load failed: %s\n", prog->name, cp); pr_warn("prog '%s': BPF program load failed: %s\n", prog->name, cp);
pr_perm_msg(ret); pr_perm_msg(ret);
...@@ -6828,10 +6872,6 @@ static int bpf_object_load_prog_instance(struct bpf_object *obj, struct bpf_prog ...@@ -6828,10 +6872,6 @@ static int bpf_object_load_prog_instance(struct bpf_object *obj, struct bpf_prog
pr_warn("prog '%s': -- BEGIN PROG LOAD LOG --\n%s-- END PROG LOAD LOG --\n", pr_warn("prog '%s': -- BEGIN PROG LOAD LOG --\n%s-- END PROG LOAD LOG --\n",
prog->name, log_buf); prog->name, log_buf);
} }
if (insns_cnt >= BPF_MAXINSNS) {
pr_warn("prog '%s': program too large (%d insns), at most %d insns\n",
prog->name, insns_cnt, BPF_MAXINSNS);
}
out: out:
if (own_log_buf) if (own_log_buf)
...@@ -6839,6 +6879,128 @@ static int bpf_object_load_prog_instance(struct bpf_object *obj, struct bpf_prog ...@@ -6839,6 +6879,128 @@ static int bpf_object_load_prog_instance(struct bpf_object *obj, struct bpf_prog
return ret; return ret;
} }
static char *find_prev_line(char *buf, char *cur)
{
char *p;
if (cur == buf) /* end of a log buf */
return NULL;
p = cur - 1;
while (p - 1 >= buf && *(p - 1) != '\n')
p--;
return p;
}
static void patch_log(char *buf, size_t buf_sz, size_t log_sz,
char *orig, size_t orig_sz, const char *patch)
{
/* size of the remaining log content to the right from the to-be-replaced part */
size_t rem_sz = (buf + log_sz) - (orig + orig_sz);
size_t patch_sz = strlen(patch);
if (patch_sz != orig_sz) {
/* If patch line(s) are longer than original piece of verifier log,
* shift log contents by (patch_sz - orig_sz) bytes to the right
* starting from after to-be-replaced part of the log.
*
* If patch line(s) are shorter than original piece of verifier log,
* shift log contents by (orig_sz - patch_sz) bytes to the left
* starting from after to-be-replaced part of the log
*
* We need to be careful about not overflowing available
* buf_sz capacity. If that's the case, we'll truncate the end
* of the original log, as necessary.
*/
if (patch_sz > orig_sz) {
if (orig + patch_sz >= buf + buf_sz) {
/* patch is big enough to cover remaining space completely */
patch_sz -= (orig + patch_sz) - (buf + buf_sz) + 1;
rem_sz = 0;
} else if (patch_sz - orig_sz > buf_sz - log_sz) {
/* patch causes part of remaining log to be truncated */
rem_sz -= (patch_sz - orig_sz) - (buf_sz - log_sz);
}
}
/* shift remaining log to the right by calculated amount */
memmove(orig + patch_sz, orig + orig_sz, rem_sz);
}
memcpy(orig, patch, patch_sz);
}
static void fixup_log_failed_core_relo(struct bpf_program *prog,
char *buf, size_t buf_sz, size_t log_sz,
char *line1, char *line2, char *line3)
{
/* Expected log for failed and not properly guarded CO-RE relocation:
* line1 -> 123: (85) call unknown#195896080
* line2 -> invalid func unknown#195896080
* line3 -> <anything else or end of buffer>
*
* "123" is the index of the instruction that was poisoned. We extract
* instruction index to find corresponding CO-RE relocation and
* replace this part of the log with more relevant information about
* failed CO-RE relocation.
*/
const struct bpf_core_relo *relo;
struct bpf_core_spec spec;
char patch[512], spec_buf[256];
int insn_idx, err;
if (sscanf(line1, "%d: (%*d) call unknown#195896080\n", &insn_idx) != 1)
return;
relo = find_relo_core(prog, insn_idx);
if (!relo)
return;
err = bpf_core_parse_spec(prog->name, prog->obj->btf, relo, &spec);
if (err)
return;
bpf_core_format_spec(spec_buf, sizeof(spec_buf), &spec);
snprintf(patch, sizeof(patch),
"%d: <invalid CO-RE relocation>\n"
"failed to resolve CO-RE relocation %s\n",
insn_idx, spec_buf);
patch_log(buf, buf_sz, log_sz, line1, line3 - line1, patch);
}
static void fixup_verifier_log(struct bpf_program *prog, char *buf, size_t buf_sz)
{
/* look for familiar error patterns in last N lines of the log */
const size_t max_last_line_cnt = 10;
char *prev_line, *cur_line, *next_line;
size_t log_sz;
int i;
if (!buf)
return;
log_sz = strlen(buf) + 1;
next_line = buf + log_sz - 1;
for (i = 0; i < max_last_line_cnt; i++, next_line = cur_line) {
cur_line = find_prev_line(buf, next_line);
if (!cur_line)
return;
/* failed CO-RE relocation case */
if (str_has_pfx(cur_line, "invalid func unknown#195896080\n")) {
prev_line = find_prev_line(buf, cur_line);
if (!prev_line)
continue;
fixup_log_failed_core_relo(prog, buf, buf_sz, log_sz,
prev_line, cur_line, next_line);
return;
}
}
}
static int bpf_program_record_relos(struct bpf_program *prog) static int bpf_program_record_relos(struct bpf_program *prog)
{ {
struct bpf_object *obj = prog->obj; struct bpf_object *obj = prog->obj;
...@@ -6993,8 +7155,8 @@ bpf_object__load_progs(struct bpf_object *obj, int log_level) ...@@ -6993,8 +7155,8 @@ bpf_object__load_progs(struct bpf_object *obj, int log_level)
if (err) if (err)
return err; return err;
} }
if (obj->gen_loader)
bpf_object__free_relocs(obj); bpf_object__free_relocs(obj);
return 0; return 0;
} }
......
...@@ -376,6 +376,13 @@ struct btf_ext_info { ...@@ -376,6 +376,13 @@ struct btf_ext_info {
void *info; void *info;
__u32 rec_size; __u32 rec_size;
__u32 len; __u32 len;
/* optional (maintained internally by libbpf) mapping between .BTF.ext
* section and corresponding ELF section. This is used to join
* information like CO-RE relocation records with corresponding BPF
* programs defined in ELF sections
*/
__u32 *sec_idxs;
int sec_cnt;
}; };
#define for_each_btf_ext_sec(seg, sec) \ #define for_each_btf_ext_sec(seg, sec) \
......
...@@ -178,29 +178,28 @@ static bool core_relo_is_enumval_based(enum bpf_core_relo_kind kind) ...@@ -178,29 +178,28 @@ static bool core_relo_is_enumval_based(enum bpf_core_relo_kind kind)
* Enum value-based relocations (ENUMVAL_EXISTS/ENUMVAL_VALUE) use access * Enum value-based relocations (ENUMVAL_EXISTS/ENUMVAL_VALUE) use access
* string to specify enumerator's value index that need to be relocated. * string to specify enumerator's value index that need to be relocated.
*/ */
static int bpf_core_parse_spec(const char *prog_name, const struct btf *btf, int bpf_core_parse_spec(const char *prog_name, const struct btf *btf,
__u32 type_id, const struct bpf_core_relo *relo,
const char *spec_str, struct bpf_core_spec *spec)
enum bpf_core_relo_kind relo_kind,
struct bpf_core_spec *spec)
{ {
int access_idx, parsed_len, i; int access_idx, parsed_len, i;
struct bpf_core_accessor *acc; struct bpf_core_accessor *acc;
const struct btf_type *t; const struct btf_type *t;
const char *name; const char *name, *spec_str;
__u32 id; __u32 id;
__s64 sz; __s64 sz;
spec_str = btf__name_by_offset(btf, relo->access_str_off);
if (str_is_empty(spec_str) || *spec_str == ':') if (str_is_empty(spec_str) || *spec_str == ':')
return -EINVAL; return -EINVAL;
memset(spec, 0, sizeof(*spec)); memset(spec, 0, sizeof(*spec));
spec->btf = btf; spec->btf = btf;
spec->root_type_id = type_id; spec->root_type_id = relo->type_id;
spec->relo_kind = relo_kind; spec->relo_kind = relo->kind;
/* type-based relocations don't have a field access string */ /* type-based relocations don't have a field access string */
if (core_relo_is_type_based(relo_kind)) { if (core_relo_is_type_based(relo->kind)) {
if (strcmp(spec_str, "0")) if (strcmp(spec_str, "0"))
return -EINVAL; return -EINVAL;
return 0; return 0;
...@@ -221,7 +220,7 @@ static int bpf_core_parse_spec(const char *prog_name, const struct btf *btf, ...@@ -221,7 +220,7 @@ static int bpf_core_parse_spec(const char *prog_name, const struct btf *btf,
if (spec->raw_len == 0) if (spec->raw_len == 0)
return -EINVAL; return -EINVAL;
t = skip_mods_and_typedefs(btf, type_id, &id); t = skip_mods_and_typedefs(btf, relo->type_id, &id);
if (!t) if (!t)
return -EINVAL; return -EINVAL;
...@@ -231,7 +230,7 @@ static int bpf_core_parse_spec(const char *prog_name, const struct btf *btf, ...@@ -231,7 +230,7 @@ static int bpf_core_parse_spec(const char *prog_name, const struct btf *btf,
acc->idx = access_idx; acc->idx = access_idx;
spec->len++; spec->len++;
if (core_relo_is_enumval_based(relo_kind)) { if (core_relo_is_enumval_based(relo->kind)) {
if (!btf_is_enum(t) || spec->raw_len > 1 || access_idx >= btf_vlen(t)) if (!btf_is_enum(t) || spec->raw_len > 1 || access_idx >= btf_vlen(t))
return -EINVAL; return -EINVAL;
...@@ -240,7 +239,7 @@ static int bpf_core_parse_spec(const char *prog_name, const struct btf *btf, ...@@ -240,7 +239,7 @@ static int bpf_core_parse_spec(const char *prog_name, const struct btf *btf,
return 0; return 0;
} }
if (!core_relo_is_field_based(relo_kind)) if (!core_relo_is_field_based(relo->kind))
return -EINVAL; return -EINVAL;
sz = btf__resolve_size(btf, id); sz = btf__resolve_size(btf, id);
...@@ -301,7 +300,7 @@ static int bpf_core_parse_spec(const char *prog_name, const struct btf *btf, ...@@ -301,7 +300,7 @@ static int bpf_core_parse_spec(const char *prog_name, const struct btf *btf,
spec->bit_offset += access_idx * sz * 8; spec->bit_offset += access_idx * sz * 8;
} else { } else {
pr_warn("prog '%s': relo for [%u] %s (at idx %d) captures type [%d] of unexpected kind %s\n", pr_warn("prog '%s': relo for [%u] %s (at idx %d) captures type [%d] of unexpected kind %s\n",
prog_name, type_id, spec_str, i, id, btf_kind_str(t)); prog_name, relo->type_id, spec_str, i, id, btf_kind_str(t));
return -EINVAL; return -EINVAL;
} }
} }
...@@ -1055,51 +1054,66 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, ...@@ -1055,51 +1054,66 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn,
* [<type-id>] (<type-name>) + <raw-spec> => <offset>@<spec>, * [<type-id>] (<type-name>) + <raw-spec> => <offset>@<spec>,
* where <spec> is a C-syntax view of recorded field access, e.g.: x.a[3].b * where <spec> is a C-syntax view of recorded field access, e.g.: x.a[3].b
*/ */
static void bpf_core_dump_spec(const char *prog_name, int level, const struct bpf_core_spec *spec) int bpf_core_format_spec(char *buf, size_t buf_sz, const struct bpf_core_spec *spec)
{ {
const struct btf_type *t; const struct btf_type *t;
const struct btf_enum *e; const struct btf_enum *e;
const char *s; const char *s;
__u32 type_id; __u32 type_id;
int i; int i, len = 0;
#define append_buf(fmt, args...) \
({ \
int r; \
r = snprintf(buf, buf_sz, fmt, ##args); \
len += r; \
if (r >= buf_sz) \
r = buf_sz; \
buf += r; \
buf_sz -= r; \
})
type_id = spec->root_type_id; type_id = spec->root_type_id;
t = btf_type_by_id(spec->btf, type_id); t = btf_type_by_id(spec->btf, type_id);
s = btf__name_by_offset(spec->btf, t->name_off); s = btf__name_by_offset(spec->btf, t->name_off);
libbpf_print(level, "[%u] %s %s", type_id, btf_kind_str(t), str_is_empty(s) ? "<anon>" : s); append_buf("<%s> [%u] %s %s",
core_relo_kind_str(spec->relo_kind),
type_id, btf_kind_str(t), str_is_empty(s) ? "<anon>" : s);
if (core_relo_is_type_based(spec->relo_kind)) if (core_relo_is_type_based(spec->relo_kind))
return; return len;
if (core_relo_is_enumval_based(spec->relo_kind)) { if (core_relo_is_enumval_based(spec->relo_kind)) {
t = skip_mods_and_typedefs(spec->btf, type_id, NULL); t = skip_mods_and_typedefs(spec->btf, type_id, NULL);
e = btf_enum(t) + spec->raw_spec[0]; e = btf_enum(t) + spec->raw_spec[0];
s = btf__name_by_offset(spec->btf, e->name_off); s = btf__name_by_offset(spec->btf, e->name_off);
libbpf_print(level, "::%s = %u", s, e->val); append_buf("::%s = %u", s, e->val);
return; return len;
} }
if (core_relo_is_field_based(spec->relo_kind)) { if (core_relo_is_field_based(spec->relo_kind)) {
for (i = 0; i < spec->len; i++) { for (i = 0; i < spec->len; i++) {
if (spec->spec[i].name) if (spec->spec[i].name)
libbpf_print(level, ".%s", spec->spec[i].name); append_buf(".%s", spec->spec[i].name);
else if (i > 0 || spec->spec[i].idx > 0) else if (i > 0 || spec->spec[i].idx > 0)
libbpf_print(level, "[%u]", spec->spec[i].idx); append_buf("[%u]", spec->spec[i].idx);
} }
libbpf_print(level, " ("); append_buf(" (");
for (i = 0; i < spec->raw_len; i++) for (i = 0; i < spec->raw_len; i++)
libbpf_print(level, "%s%d", i == 0 ? "" : ":", spec->raw_spec[i]); append_buf("%s%d", i == 0 ? "" : ":", spec->raw_spec[i]);
if (spec->bit_offset % 8) if (spec->bit_offset % 8)
libbpf_print(level, " @ offset %u.%u)", append_buf(" @ offset %u.%u)", spec->bit_offset / 8, spec->bit_offset % 8);
spec->bit_offset / 8, spec->bit_offset % 8);
else else
libbpf_print(level, " @ offset %u)", spec->bit_offset / 8); append_buf(" @ offset %u)", spec->bit_offset / 8);
return; return len;
} }
return len;
#undef append_buf
} }
/* /*
...@@ -1167,7 +1181,7 @@ int bpf_core_calc_relo_insn(const char *prog_name, ...@@ -1167,7 +1181,7 @@ int bpf_core_calc_relo_insn(const char *prog_name,
const struct btf_type *local_type; const struct btf_type *local_type;
const char *local_name; const char *local_name;
__u32 local_id; __u32 local_id;
const char *spec_str; char spec_buf[256];
int i, j, err; int i, j, err;
local_id = relo->type_id; local_id = relo->type_id;
...@@ -1176,24 +1190,20 @@ int bpf_core_calc_relo_insn(const char *prog_name, ...@@ -1176,24 +1190,20 @@ int bpf_core_calc_relo_insn(const char *prog_name,
if (!local_name) if (!local_name)
return -EINVAL; return -EINVAL;
spec_str = btf__name_by_offset(local_btf, relo->access_str_off); err = bpf_core_parse_spec(prog_name, local_btf, relo, local_spec);
if (str_is_empty(spec_str))
return -EINVAL;
err = bpf_core_parse_spec(prog_name, local_btf, local_id, spec_str,
relo->kind, local_spec);
if (err) { if (err) {
const char *spec_str;
spec_str = btf__name_by_offset(local_btf, relo->access_str_off);
pr_warn("prog '%s': relo #%d: parsing [%d] %s %s + %s failed: %d\n", pr_warn("prog '%s': relo #%d: parsing [%d] %s %s + %s failed: %d\n",
prog_name, relo_idx, local_id, btf_kind_str(local_type), prog_name, relo_idx, local_id, btf_kind_str(local_type),
str_is_empty(local_name) ? "<anon>" : local_name, str_is_empty(local_name) ? "<anon>" : local_name,
spec_str, err); spec_str ?: "<?>", err);
return -EINVAL; return -EINVAL;
} }
pr_debug("prog '%s': relo #%d: kind <%s> (%d), spec is ", prog_name, bpf_core_format_spec(spec_buf, sizeof(spec_buf), local_spec);
relo_idx, core_relo_kind_str(relo->kind), relo->kind); pr_debug("prog '%s': relo #%d: %s\n", prog_name, relo_idx, spec_buf);
bpf_core_dump_spec(prog_name, LIBBPF_DEBUG, local_spec);
libbpf_print(LIBBPF_DEBUG, "\n");
/* TYPE_ID_LOCAL relo is special and doesn't need candidate search */ /* TYPE_ID_LOCAL relo is special and doesn't need candidate search */
if (relo->kind == BPF_CORE_TYPE_ID_LOCAL) { if (relo->kind == BPF_CORE_TYPE_ID_LOCAL) {
...@@ -1207,7 +1217,7 @@ int bpf_core_calc_relo_insn(const char *prog_name, ...@@ -1207,7 +1217,7 @@ int bpf_core_calc_relo_insn(const char *prog_name,
} }
/* libbpf doesn't support candidate search for anonymous types */ /* libbpf doesn't support candidate search for anonymous types */
if (str_is_empty(spec_str)) { if (str_is_empty(local_name)) {
pr_warn("prog '%s': relo #%d: <%s> (%d) relocation doesn't support anonymous types\n", pr_warn("prog '%s': relo #%d: <%s> (%d) relocation doesn't support anonymous types\n",
prog_name, relo_idx, core_relo_kind_str(relo->kind), relo->kind); prog_name, relo_idx, core_relo_kind_str(relo->kind), relo->kind);
return -EOPNOTSUPP; return -EOPNOTSUPP;
...@@ -1217,17 +1227,15 @@ int bpf_core_calc_relo_insn(const char *prog_name, ...@@ -1217,17 +1227,15 @@ int bpf_core_calc_relo_insn(const char *prog_name,
err = bpf_core_spec_match(local_spec, cands->cands[i].btf, err = bpf_core_spec_match(local_spec, cands->cands[i].btf,
cands->cands[i].id, cand_spec); cands->cands[i].id, cand_spec);
if (err < 0) { if (err < 0) {
pr_warn("prog '%s': relo #%d: error matching candidate #%d ", bpf_core_format_spec(spec_buf, sizeof(spec_buf), cand_spec);
prog_name, relo_idx, i); pr_warn("prog '%s': relo #%d: error matching candidate #%d %s: %d\n ",
bpf_core_dump_spec(prog_name, LIBBPF_WARN, cand_spec); prog_name, relo_idx, i, spec_buf, err);
libbpf_print(LIBBPF_WARN, ": %d\n", err);
return err; return err;
} }
pr_debug("prog '%s': relo #%d: %s candidate #%d ", prog_name, bpf_core_format_spec(spec_buf, sizeof(spec_buf), cand_spec);
relo_idx, err == 0 ? "non-matching" : "matching", i); pr_debug("prog '%s': relo #%d: %s candidate #%d %s\n", prog_name,
bpf_core_dump_spec(prog_name, LIBBPF_DEBUG, cand_spec); relo_idx, err == 0 ? "non-matching" : "matching", i, spec_buf);
libbpf_print(LIBBPF_DEBUG, "\n");
if (err == 0) if (err == 0)
continue; continue;
......
...@@ -84,4 +84,10 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, ...@@ -84,4 +84,10 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn,
int insn_idx, const struct bpf_core_relo *relo, int insn_idx, const struct bpf_core_relo *relo,
int relo_idx, const struct bpf_core_relo_res *res); int relo_idx, const struct bpf_core_relo_res *res);
int bpf_core_parse_spec(const char *prog_name, const struct btf *btf,
const struct bpf_core_relo *relo,
struct bpf_core_spec *spec);
int bpf_core_format_spec(char *buf, size_t buf_sz, const struct bpf_core_spec *spec);
#endif #endif
...@@ -14,6 +14,12 @@ void test_linked_funcs(void) ...@@ -14,6 +14,12 @@ void test_linked_funcs(void)
if (!ASSERT_OK_PTR(skel, "skel_open")) if (!ASSERT_OK_PTR(skel, "skel_open"))
return; return;
/* handler1 and handler2 are marked as SEC("?raw_tp/sys_enter") and
* are set to not autoload by default
*/
bpf_program__set_autoload(skel->progs.handler1, true);
bpf_program__set_autoload(skel->progs.handler2, true);
skel->rodata->my_tid = syscall(SYS_gettid); skel->rodata->my_tid = syscall(SYS_gettid);
skel->bss->syscall_id = SYS_getpgid; skel->bss->syscall_id = SYS_getpgid;
......
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#include <test_progs.h>
#include <bpf/btf.h>
#include "test_log_fixup.skel.h"
enum trunc_type {
TRUNC_NONE,
TRUNC_PARTIAL,
TRUNC_FULL,
};
static void bad_core_relo(size_t log_buf_size, enum trunc_type trunc_type)
{
char log_buf[8 * 1024];
struct test_log_fixup* skel;
int err;
skel = test_log_fixup__open();
if (!ASSERT_OK_PTR(skel, "skel_open"))
return;
bpf_program__set_autoload(skel->progs.bad_relo, true);
memset(log_buf, 0, sizeof(log_buf));
bpf_program__set_log_buf(skel->progs.bad_relo, log_buf, log_buf_size ?: sizeof(log_buf));
err = test_log_fixup__load(skel);
if (!ASSERT_ERR(err, "load_fail"))
goto cleanup;
ASSERT_HAS_SUBSTR(log_buf,
"0: <invalid CO-RE relocation>\n"
"failed to resolve CO-RE relocation <byte_sz> ",
"log_buf_part1");
switch (trunc_type) {
case TRUNC_NONE:
ASSERT_HAS_SUBSTR(log_buf,
"struct task_struct___bad.fake_field (0:1 @ offset 4)\n",
"log_buf_part2");
ASSERT_HAS_SUBSTR(log_buf,
"max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0\n",
"log_buf_end");
break;
case TRUNC_PARTIAL:
/* we should get full libbpf message patch */
ASSERT_HAS_SUBSTR(log_buf,
"struct task_struct___bad.fake_field (0:1 @ offset 4)\n",
"log_buf_part2");
/* we shouldn't get full end of BPF verifier log */
ASSERT_NULL(strstr(log_buf, "max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0\n"),
"log_buf_end");
break;
case TRUNC_FULL:
/* we shouldn't get second part of libbpf message patch */
ASSERT_NULL(strstr(log_buf, "struct task_struct___bad.fake_field (0:1 @ offset 4)\n"),
"log_buf_part2");
/* we shouldn't get full end of BPF verifier log */
ASSERT_NULL(strstr(log_buf, "max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0\n"),
"log_buf_end");
break;
}
if (env.verbosity > VERBOSE_NONE)
printf("LOG: \n=================\n%s=================\n", log_buf);
cleanup:
test_log_fixup__destroy(skel);
}
static void bad_core_relo_subprog(void)
{
char log_buf[8 * 1024];
struct test_log_fixup* skel;
int err;
skel = test_log_fixup__open();
if (!ASSERT_OK_PTR(skel, "skel_open"))
return;
bpf_program__set_autoload(skel->progs.bad_relo_subprog, true);
bpf_program__set_log_buf(skel->progs.bad_relo_subprog, log_buf, sizeof(log_buf));
err = test_log_fixup__load(skel);
if (!ASSERT_ERR(err, "load_fail"))
goto cleanup;
/* there should be no prog loading log because we specified per-prog log buf */
ASSERT_HAS_SUBSTR(log_buf,
": <invalid CO-RE relocation>\n"
"failed to resolve CO-RE relocation <byte_off> ",
"log_buf");
ASSERT_HAS_SUBSTR(log_buf,
"struct task_struct___bad.fake_field_subprog (0:2 @ offset 8)\n",
"log_buf");
if (env.verbosity > VERBOSE_NONE)
printf("LOG: \n=================\n%s=================\n", log_buf);
cleanup:
test_log_fixup__destroy(skel);
}
void test_log_fixup(void)
{
if (test__start_subtest("bad_core_relo_trunc_none"))
bad_core_relo(0, TRUNC_NONE /* full buf */);
if (test__start_subtest("bad_core_relo_trunc_partial"))
bad_core_relo(300, TRUNC_PARTIAL /* truncate original log a bit */);
if (test__start_subtest("bad_core_relo_trunc_full"))
bad_core_relo(250, TRUNC_FULL /* truncate also libbpf's message patch */);
if (test__start_subtest("bad_core_relo_subprog"))
bad_core_relo_subprog();
}
...@@ -61,12 +61,17 @@ extern int set_output_val2(int x); ...@@ -61,12 +61,17 @@ extern int set_output_val2(int x);
/* here we'll force set_output_ctx2() to be __hidden in the final obj file */ /* here we'll force set_output_ctx2() to be __hidden in the final obj file */
__hidden extern void set_output_ctx2(__u64 *ctx); __hidden extern void set_output_ctx2(__u64 *ctx);
SEC("raw_tp/sys_enter") SEC("?raw_tp/sys_enter")
int BPF_PROG(handler1, struct pt_regs *regs, long id) int BPF_PROG(handler1, struct pt_regs *regs, long id)
{ {
static volatile int whatever;
if (my_tid != (u32)bpf_get_current_pid_tgid() || id != syscall_id) if (my_tid != (u32)bpf_get_current_pid_tgid() || id != syscall_id)
return 0; return 0;
/* make sure we have CO-RE relocations in main program */
whatever = bpf_core_type_size(struct task_struct);
set_output_val2(1000); set_output_val2(1000);
set_output_ctx2(ctx); /* ctx definition is hidden in BPF_PROG macro */ set_output_ctx2(ctx); /* ctx definition is hidden in BPF_PROG macro */
......
...@@ -61,12 +61,17 @@ extern int set_output_val1(int x); ...@@ -61,12 +61,17 @@ extern int set_output_val1(int x);
/* here we'll force set_output_ctx1() to be __hidden in the final obj file */ /* here we'll force set_output_ctx1() to be __hidden in the final obj file */
__hidden extern void set_output_ctx1(__u64 *ctx); __hidden extern void set_output_ctx1(__u64 *ctx);
SEC("raw_tp/sys_enter") SEC("?raw_tp/sys_enter")
int BPF_PROG(handler2, struct pt_regs *regs, long id) int BPF_PROG(handler2, struct pt_regs *regs, long id)
{ {
static volatile int whatever;
if (my_tid != (u32)bpf_get_current_pid_tgid() || id != syscall_id) if (my_tid != (u32)bpf_get_current_pid_tgid() || id != syscall_id)
return 0; return 0;
/* make sure we have CO-RE relocations in main program */
whatever = bpf_core_type_size(struct task_struct);
set_output_val1(2000); set_output_val1(2000);
set_output_ctx1(ctx); /* ctx definition is hidden in BPF_PROG macro */ set_output_ctx1(ctx); /* ctx definition is hidden in BPF_PROG macro */
......
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
struct task_struct___bad {
int pid;
int fake_field;
void *fake_field_subprog;
} __attribute__((preserve_access_index));
SEC("?raw_tp/sys_enter")
int bad_relo(const void *ctx)
{
static struct task_struct___bad *t;
return bpf_core_field_size(t->fake_field);
}
static __noinline int bad_subprog(void)
{
static struct task_struct___bad *t;
/* ugliness below is a field offset relocation */
return (void *)&t->fake_field_subprog - (void *)t;
}
SEC("?raw_tp/sys_enter")
int bad_relo_subprog(const void *ctx)
{
static struct task_struct___bad *t;
return bad_subprog() + bpf_core_field_size(t->pid);
}
char _license[] SEC("license") = "GPL";
...@@ -292,6 +292,17 @@ int test__join_cgroup(const char *path); ...@@ -292,6 +292,17 @@ int test__join_cgroup(const char *path);
___ok; \ ___ok; \
}) })
#define ASSERT_HAS_SUBSTR(str, substr, name) ({ \
static int duration = 0; \
const char *___str = str; \
const char *___substr = substr; \
bool ___ok = strstr(___str, ___substr) != NULL; \
CHECK(!___ok, (name), \
"unexpected %s: '%s' is not a substring of '%s'\n", \
(name), ___substr, ___str); \
___ok; \
})
#define ASSERT_OK(res, name) ({ \ #define ASSERT_OK(res, name) ({ \
static int duration = 0; \ static int duration = 0; \
long long ___res = (res); \ long long ___res = (res); \
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment