struct src_sec { constchar *sec_name; /* positional (not necessarily ELF) index in an array of sections */ int id; /* positional (not necessarily ELF) index of a matching section in a final object file */ int dst_id; /* section data offset in a matching output section */ int dst_off; /* whether section is omitted from the final ELF file */ bool skipped; /* whether section is an ephemeral section, not mapped to an ELF section */ bool ephemeral;
/* corresponding BTF DATASEC type ID */ int sec_type_id;
};
struct src_obj { constchar *filename; int fd;
Elf *elf; /* Section header strings section index */
size_t shstrs_sec_idx; /* SYMTAB section index */
size_t symtab_sec_idx;
struct btf *btf; struct btf_ext *btf_ext;
/* List of sections (including ephemeral). Slot zero is unused. */ struct src_sec *secs; int sec_cnt;
/* mapping of symbol indices from src to dst ELF */ int *sym_map; /* mapping from the src BTF type IDs to dst ones */ int *btf_type_map;
};
/* single .BTF.ext data section */ struct btf_ext_sec_data {
size_t rec_cnt;
__u32 rec_sz; void *recs;
};
struct glob_sym { /* ELF symbol index */ int sym_idx; /* associated section id for .ksyms, .kconfig, etc, but not .extern */ int sec_id; /* extern name offset in STRTAB */ int name_off; /* optional associated BTF type ID */ int btf_id; /* BTF type ID to which VAR/FUNC type is pointing to; used for * rewriting types when extern VAR/FUNC is resolved to a concrete * definition
*/ int underlying_btf_id; /* sec_var index in the corresponding dst_sec, if exists */ int var_idx;
/* extern or resolved/global symbol */ bool is_extern; /* weak or strong symbol, never goes back from strong to weak */ bool is_weak;
};
struct dst_sec { char *sec_name; /* positional (not necessarily ELF) index in an array of sections */ int id;
linker->elf = elf_begin(linker->fd, ELF_C_WRITE, NULL); if (!linker->elf) {
pr_warn_elf("failed to create ELF object"); return -EINVAL;
}
/* ELF header */
linker->elf_hdr = elf64_newehdr(linker->elf); if (!linker->elf_hdr) {
pr_warn_elf("failed to create ELF header"); return -EINVAL;
}
linker->elf_hdr->e_machine = EM_BPF;
linker->elf_hdr->e_type = ET_REL; /* Set unknown ELF endianness, assign later from input files */
linker->elf_hdr->e_ident[EI_DATA] = ELFDATANONE;
/* STRTAB */ /* initialize strset with an empty string to conform to ELF */
linker->strtab_strs = strset__new(INT_MAX, "", sizeof("")); if (libbpf_get_error(linker->strtab_strs)) return libbpf_get_error(linker->strtab_strs);
sec = add_dst_sec(linker, ".strtab"); if (!sec) return -ENOMEM;
sec->scn = elf_newscn(linker->elf); if (!sec->scn) {
pr_warn_elf("failed to create STRTAB section"); return -EINVAL;
}
sec->shdr = elf64_getshdr(sec->scn); if (!sec->shdr) return -EINVAL;
sec->data = elf_newdata(sec->scn); if (!sec->data) {
pr_warn_elf("failed to create STRTAB data"); return -EINVAL;
}
str_off = strset__add_str(linker->strtab_strs, sec->sec_name); if (str_off < 0) return str_off;
sec->shdr->sh_name = str_off;
sec->shdr->sh_type = SHT_SYMTAB;
sec->shdr->sh_flags = 0;
sec->shdr->sh_offset = 0;
sec->shdr->sh_link = linker->strtab_sec_idx; /* sh_info should be one greater than the index of the last local * symbol (i.e., binding is STB_LOCAL). But why and who cares?
*/
sec->shdr->sh_info = 0;
sec->shdr->sh_addralign = 8;
sec->shdr->sh_entsize = sizeof(Elf64_Sym);
fd = sys_memfd_create(filename, 0); if (fd < 0) {
ret = -errno;
pr_warn("failed to create memfd '%s': %s\n", filename, errstr(ret)); return libbpf_err(ret);
}
written = 0; while (written < buf_sz) {
ret = write(fd, buf, buf_sz); if (ret < 0) {
ret = -errno;
pr_warn("failed to write '%s': %s\n", filename, errstr(ret)); goto err_out;
}
written += ret;
}
ret = bpf_linker_add_file(linker, fd, filename);
err_out:
close(fd); return libbpf_err(ret);
}
staticbool is_dwarf_sec_name(constchar *name)
{ /* approximation, but the actual list is too long */ return strncmp(name, ".debug_", sizeof(".debug_") - 1) == 0;
}
/* no special handling of .strtab */ if (shdr->sh_type == SHT_STRTAB) returntrue;
/* ignore .llvm_addrsig section as well */ if (shdr->sh_type == SHT_LLVM_ADDRSIG) returntrue;
/* no subprograms will lead to an empty .text section, ignore it */ if (shdr->sh_type == SHT_PROGBITS && shdr->sh_size == 0 &&
strcmp(sec->sec_name, ".text") == 0) returntrue;
/* DWARF sections */ if (is_dwarf_sec_name(sec->sec_name)) returntrue;
if (strncmp(name, ".rel", sizeof(".rel") - 1) == 0) {
name += sizeof(".rel") - 1; /* DWARF section relocations */ if (is_dwarf_sec_name(name)) returntrue;
/* .BTF and .BTF.ext don't need relocations */ if (strcmp(name, BTF_ELF_SEC) == 0 ||
strcmp(name, BTF_EXT_ELF_SEC) == 0) returntrue;
}
shdr = elf64_getshdr(scn); if (!shdr) {
pr_warn_elf("failed to get section #%zu header for %s",
sec_idx, obj->filename); return -EINVAL;
}
sec_name = elf_strptr(obj->elf, obj->shstrs_sec_idx, shdr->sh_name); if (!sec_name) {
pr_warn_elf("failed to get section #%zu name for %s",
sec_idx, obj->filename); return -EINVAL;
}
data = elf_getdata(scn, 0); if (!data) {
pr_warn_elf("failed to get section #%zu (%s) data from %s",
sec_idx, sec_name, obj->filename); return -EINVAL;
}
sec = add_src_sec(obj, sec_name); if (!sec) return -ENOMEM;
staticint linker_sanity_check_elf(struct src_obj *obj)
{ struct src_sec *sec; int i, err;
if (!obj->symtab_sec_idx) {
pr_warn("ELF is missing SYMTAB section in %s\n", obj->filename); return -EINVAL;
} if (!obj->shstrs_sec_idx) {
pr_warn("ELF is missing section headers STRTAB section in %s\n", obj->filename); return -EINVAL;
}
for (i = 1; i < obj->sec_cnt; i++) {
sec = &obj->secs[i];
if (sec->sec_name[0] == '\0') {
pr_warn("ELF section #%zu has empty name in %s\n", sec->sec_idx, obj->filename); return -EINVAL;
}
if (is_dwarf_sec_name(sec->sec_name)) continue;
if (sec->shdr->sh_addralign && !is_pow_of_2(sec->shdr->sh_addralign)) {
pr_warn("ELF section #%zu alignment %llu is non pow-of-2 alignment in %s\n",
sec->sec_idx, (longlongunsigned)sec->shdr->sh_addralign,
obj->filename); return -EINVAL;
} if (sec->shdr->sh_addralign != sec->data->d_align) {
pr_warn("ELF section #%zu has inconsistent alignment addr=%llu != d=%llu in %s\n",
sec->sec_idx, (longlongunsigned)sec->shdr->sh_addralign,
(longlongunsigned)sec->data->d_align, obj->filename); return -EINVAL;
}
if (sec->shdr->sh_size != sec->data->d_size) {
pr_warn("ELF section #%zu has inconsistent section size sh=%llu != d=%llu in %s\n",
sec->sec_idx, (longlongunsigned)sec->shdr->sh_size,
(longlongunsigned)sec->data->d_size, obj->filename); return -EINVAL;
}
switch (sec->shdr->sh_type) { case SHT_SYMTAB:
err = linker_sanity_check_elf_symtab(obj, sec); if (err) return err; break; case SHT_STRTAB: break; case SHT_PROGBITS: if (sec->shdr->sh_flags & SHF_EXECINSTR) { if (sec->shdr->sh_size % sizeof(struct bpf_insn) != 0) {
pr_warn("ELF section #%zu has unexpected size alignment %llu in %s\n",
sec->sec_idx, (longlongunsigned)sec->shdr->sh_size,
obj->filename); return -EINVAL;
}
} break; case SHT_NOBITS: break; case SHT_REL:
err = linker_sanity_check_elf_relos(obj, sec); if (err) return err; break; case SHT_LLVM_ADDRSIG: break; default:
pr_warn("ELF section #%zu (%s) has unrecognized type %zu in %s\n",
sec->sec_idx, sec->sec_name, (size_t)sec->shdr->sh_type, obj->filename); return -EINVAL;
}
}
return 0;
}
staticint linker_sanity_check_elf_symtab(struct src_obj *obj, struct src_sec *sec)
{ struct src_sec *link_sec;
Elf64_Sym *sym; int i, n;
if (sec->shdr->sh_entsize != sizeof(Elf64_Sym)) return -EINVAL; if (sec->shdr->sh_size % sec->shdr->sh_entsize != 0) return -EINVAL;
if (!sec->shdr->sh_link || sec->shdr->sh_link >= obj->sec_cnt) {
pr_warn("ELF SYMTAB section #%zu points to missing STRTAB section #%zu in %s\n",
sec->sec_idx, (size_t)sec->shdr->sh_link, obj->filename); return -EINVAL;
}
link_sec = &obj->secs[sec->shdr->sh_link]; if (link_sec->shdr->sh_type != SHT_STRTAB) {
pr_warn("ELF SYMTAB section #%zu points to invalid STRTAB section #%zu in %s\n",
sec->sec_idx, (size_t)sec->shdr->sh_link, obj->filename); return -EINVAL;
}
n = sec->shdr->sh_size / sec->shdr->sh_entsize;
sym = sec->data->d_buf; for (i = 0; i < n; i++, sym++) { int sym_type = ELF64_ST_TYPE(sym->st_info); int sym_bind = ELF64_ST_BIND(sym->st_info); int sym_vis = ELF64_ST_VISIBILITY(sym->st_other);
if (i == 0) { if (sym->st_name != 0 || sym->st_info != 0
|| sym->st_other != 0 || sym->st_shndx != 0
|| sym->st_value != 0 || sym->st_size != 0) {
pr_warn("ELF sym #0 is invalid in %s\n", obj->filename); return -EINVAL;
} continue;
} if (sym_bind != STB_LOCAL && sym_bind != STB_GLOBAL && sym_bind != STB_WEAK) {
pr_warn("ELF sym #%d in section #%zu has unsupported symbol binding %d\n",
i, sec->sec_idx, sym_bind); return -EINVAL;
} if (sym_vis != STV_DEFAULT && sym_vis != STV_HIDDEN) {
pr_warn("ELF sym #%d in section #%zu has unsupported symbol visibility %d\n",
i, sec->sec_idx, sym_vis); return -EINVAL;
} if (sym->st_shndx == 0) { if (sym_type != STT_NOTYPE || sym_bind == STB_LOCAL
|| sym->st_value != 0 || sym->st_size != 0) {
pr_warn("ELF sym #%d is invalid extern symbol in %s\n",
i, obj->filename);
return -EINVAL;
} continue;
} if (sym->st_shndx < SHN_LORESERVE && sym->st_shndx >= obj->sec_cnt) {
pr_warn("ELF sym #%d in section #%zu points to missing section #%zu in %s\n",
i, sec->sec_idx, (size_t)sym->st_shndx, obj->filename); return -EINVAL;
} if (sym_type == STT_SECTION) { if (sym->st_value != 0) return -EINVAL; continue;
}
}
return 0;
}
staticint linker_sanity_check_elf_relos(struct src_obj *obj, struct src_sec *sec)
{ struct src_sec *link_sec, *sym_sec;
Elf64_Rel *relo; int i, n;
if (sec->shdr->sh_entsize != sizeof(Elf64_Rel)) return -EINVAL; if (sec->shdr->sh_size % sec->shdr->sh_entsize != 0) return -EINVAL;
/* SHT_REL's sh_link should point to SYMTAB */ if (sec->shdr->sh_link != obj->symtab_sec_idx) {
pr_warn("ELF relo section #%zu points to invalid SYMTAB section #%zu in %s\n",
sec->sec_idx, (size_t)sec->shdr->sh_link, obj->filename); return -EINVAL;
}
/* SHT_REL's sh_info points to relocated section */ if (!sec->shdr->sh_info || sec->shdr->sh_info >= obj->sec_cnt) {
pr_warn("ELF relo section #%zu points to missing section #%zu in %s\n",
sec->sec_idx, (size_t)sec->shdr->sh_info, obj->filename); return -EINVAL;
}
link_sec = &obj->secs[sec->shdr->sh_info];
/* .rel<secname> -> <secname> pattern is followed */ if (strncmp(sec->sec_name, ".rel", sizeof(".rel") - 1) != 0
|| strcmp(sec->sec_name + sizeof(".rel") - 1, link_sec->sec_name) != 0) {
pr_warn("ELF relo section #%zu name has invalid name in %s\n",
sec->sec_idx, obj->filename); return -EINVAL;
}
/* don't further validate relocations for ignored sections */ if (link_sec->skipped) return 0;
/* relocatable section is data or instructions */ if (link_sec->shdr->sh_type != SHT_PROGBITS && link_sec->shdr->sh_type != SHT_NOBITS) {
pr_warn("ELF relo section #%zu points to invalid section #%zu in %s\n",
sec->sec_idx, (size_t)sec->shdr->sh_info, obj->filename); return -EINVAL;
}
/* check sanity of each relocation */
n = sec->shdr->sh_size / sec->shdr->sh_entsize;
relo = sec->data->d_buf;
sym_sec = &obj->secs[obj->symtab_sec_idx]; for (i = 0; i < n; i++, relo++) {
size_t sym_idx = ELF64_R_SYM(relo->r_info);
size_t sym_type = ELF64_R_TYPE(relo->r_info);
if (sym_type != R_BPF_64_64 && sym_type != R_BPF_64_32 &&
sym_type != R_BPF_64_ABS64 && sym_type != R_BPF_64_ABS32) {
pr_warn("ELF relo #%d in section #%zu has unexpected type %zu in %s\n",
i, sec->sec_idx, sym_type, obj->filename); return -EINVAL;
}
if (!sym_idx || sym_idx * sizeof(Elf64_Sym) >= sym_sec->shdr->sh_size) {
pr_warn("ELF relo #%d in section #%zu points to invalid symbol #%zu in %s\n",
i, sec->sec_idx, sym_idx, obj->filename); return -EINVAL;
}
if (link_sec->shdr->sh_flags & SHF_EXECINSTR) { if (relo->r_offset % sizeof(struct bpf_insn) != 0) {
pr_warn("ELF relo #%d in section #%zu points to missing symbol #%zu in %s\n",
i, sec->sec_idx, sym_idx, obj->filename); return -EINVAL;
}
}
}
name_off = strset__add_str(linker->strtab_strs, src_sec->sec_name); if (name_off < 0) return name_off;
shdr->sh_name = name_off;
shdr->sh_type = src_sec->shdr->sh_type;
shdr->sh_flags = src_sec->shdr->sh_flags;
shdr->sh_size = 0; /* sh_link and sh_info have different meaning for different types of * sections, so we leave it up to the caller code to fill them in, if * necessary
*/
shdr->sh_link = 0;
shdr->sh_info = 0;
shdr->sh_addralign = src_sec->shdr->sh_addralign;
shdr->sh_entsize = src_sec->shdr->sh_entsize;
/* Ephemeral source section doesn't contribute anything to ELF * section data.
*/ if (src->ephemeral) return 0;
/* Some sections (like .maps) can contain both externs (and thus be * ephemeral) and non-externs (map definitions). So it's possible that * it has to be "upgraded" from ephemeral to non-ephemeral when the * first non-ephemeral entity appears. In such case, we add ELF * section, data, etc.
*/ if (dst->ephemeral) {
err = init_sec(linker, dst, src); if (err) return err;
}
dst_align = dst->shdr->sh_addralign;
src_align = src->shdr->sh_addralign; if (dst_align == 0)
dst_align = 1; if (dst_align < src_align)
dst_align = src_align;
/* no need to re-align final size */
dst_final_sz = dst_align_sz + src->shdr->sh_size;
if (src->shdr->sh_type != SHT_NOBITS) {
tmp = realloc(dst->raw_data, dst_final_sz); /* If dst_align_sz == 0, realloc() behaves in a special way: * 1. When dst->raw_data is NULL it returns: * "either NULL or a pointer suitable to be passed to free()" [1]. * 2. When dst->raw_data is not-NULL it frees dst->raw_data and returns NULL, * thus invalidating any "pointer suitable to be passed to free()" obtained * at step (1). * * The dst_align_sz > 0 check avoids error exit after (2), otherwise * dst->raw_data would be freed again in bpf_linker__free(). * * [1] man 3 realloc
*/ if (!tmp && dst_align_sz > 0) return -ENOMEM;
dst->raw_data = tmp;
/* pad dst section, if it's alignment forced size increase */
memset(dst->raw_data + dst->sec_sz, 0, dst_align_sz - dst->sec_sz); /* now copy src data at a properly aligned offset */
memcpy(dst->raw_data + dst_align_sz, src->data->d_buf, src->shdr->sh_size);
/* convert added bpf insns to native byte-order */ if (linker->swapped_endian && is_exec_sec(dst))
exec_sec_bswap(dst->raw_data + dst_align_sz, src->shdr->sh_size);
}
staticint linker_append_sec_data(struct bpf_linker *linker, struct src_obj *obj)
{ int i, err;
for (i = 1; i < obj->sec_cnt; i++) { struct src_sec *src_sec; struct dst_sec *dst_sec;
src_sec = &obj->secs[i]; if (!is_data_sec(src_sec)) continue;
dst_sec = find_dst_sec_by_name(linker, src_sec->sec_name); if (!dst_sec) {
dst_sec = add_dst_sec(linker, src_sec->sec_name); if (!dst_sec) return -ENOMEM;
err = init_sec(linker, dst_sec, src_sec); if (err) {
pr_warn("failed to init section '%s'\n", src_sec->sec_name); return err;
}
} else { if (!secs_match(dst_sec, src_sec)) {
pr_warn("ELF sections %s are incompatible\n", src_sec->sec_name); return -EINVAL;
}
/* "license" and "version" sections are deduped */ if (strcmp(src_sec->sec_name, "license") == 0
|| strcmp(src_sec->sec_name, "version") == 0) { if (!sec_content_is_same(dst_sec, src_sec)) {
pr_warn("non-identical contents of section '%s' are not supported\n", src_sec->sec_name); return -EINVAL;
}
src_sec->skipped = true;
src_sec->dst_id = dst_sec->id; continue;
}
}
/* record mapped section index */
src_sec->dst_id = dst_sec->id;
err = extend_sec(linker, dst_sec, src_sec); if (err) return err;
}
return 0;
}
staticint linker_append_elf_syms(struct bpf_linker *linker, struct src_obj *obj)
{ struct src_sec *symtab = &obj->secs[obj->symtab_sec_idx];
Elf64_Sym *sym = symtab->data->d_buf; int i, n = symtab->shdr->sh_size / symtab->shdr->sh_entsize, err; int str_sec_idx = symtab->shdr->sh_link; constchar *sym_name;
obj->sym_map = calloc(n + 1, sizeof(*obj->sym_map)); if (!obj->sym_map) return -ENOMEM;
for (i = 0; i < n; i++, sym++) { /* We already validated all-zero symbol #0 and we already * appended it preventively to the final SYMTAB, so skip it.
*/ if (i == 0) continue;
sym_name = elf_strptr(obj->elf, str_sec_idx, sym->st_name); if (!sym_name) {
pr_warn("can't fetch symbol name for symbol #%d in '%s'\n", i, obj->filename); return -EINVAL;
}
/* check if only one side is FWD, otherwise handle with common logic */ if (!exact && btf_is_fwd(t1) != btf_is_fwd(t2)) {
n1 = btf__str_by_offset(btf1, t1->name_off);
n2 = btf__str_by_offset(btf2, t2->name_off); if (strcmp(n1, n2) != 0) {
pr_warn("global '%s': incompatible forward declaration names '%s' and '%s'\n",
sym_name, n1, n2); returnfalse;
} /* validate if FWD kind matches concrete kind */ if (btf_is_fwd(t1)) { if (btf_kflag(t1) && btf_is_union(t2)) returntrue; if (!btf_kflag(t1) && btf_is_struct(t2)) returntrue;
pr_warn("global '%s': incompatible %s forward declaration and concrete kind %s\n",
sym_name, btf_kflag(t1) ? "union" : "struct", btf_kind_str(t2));
} else { if (btf_kflag(t2) && btf_is_union(t1)) returntrue; if (!btf_kflag(t2) && btf_is_struct(t1)) returntrue;
pr_warn("global '%s': incompatible %s forward declaration and concrete kind %s\n",
sym_name, btf_kflag(t2) ? "union" : "struct", btf_kind_str(t1));
} returnfalse;
}
if (btf_kind(t1) != btf_kind(t2)) {
pr_warn("global '%s': incompatible BTF kinds %s and %s\n",
sym_name, btf_kind_str(t1), btf_kind_str(t2)); returnfalse;
}
switch (btf_kind(t1)) { case BTF_KIND_STRUCT: case BTF_KIND_UNION: case BTF_KIND_ENUM: case BTF_KIND_ENUM64: case BTF_KIND_FWD: case BTF_KIND_FUNC: case BTF_KIND_VAR:
n1 = btf__str_by_offset(btf1, t1->name_off);
n2 = btf__str_by_offset(btf2, t2->name_off); if (strcmp(n1, n2) != 0) {
pr_warn("global '%s': incompatible %s names '%s' and '%s'\n",
sym_name, btf_kind_str(t1), n1, n2); returnfalse;
} break; default: break;
}
switch (btf_kind(t1)) { case BTF_KIND_UNKN: /* void */ case BTF_KIND_FWD: returntrue; case BTF_KIND_INT: case BTF_KIND_FLOAT: case BTF_KIND_ENUM: case BTF_KIND_ENUM64: /* ignore encoding for int and enum values for enum */ if (t1->size != t2->size) {
pr_warn("global '%s': incompatible %s '%s' size %u and %u\n",
sym_name, btf_kind_str(t1), n1, t1->size, t2->size); returnfalse;
} returntrue; case BTF_KIND_PTR: /* just validate overall shape of the referenced type, so no * contents comparison for struct/union, and allowed fwd vs * struct/union
*/
exact = false;
id1 = t1->type;
id2 = t2->type; goto recur; case BTF_KIND_ARRAY: /* ignore index type and array size */
id1 = btf_array(t1)->type;
id2 = btf_array(t2)->type; goto recur; case BTF_KIND_FUNC: /* extern and global linkages are compatible */
is_static1 = btf_func_linkage(t1) == BTF_FUNC_STATIC;
is_static2 = btf_func_linkage(t2) == BTF_FUNC_STATIC; if (is_static1 != is_static2) {
pr_warn("global '%s': incompatible func '%s' linkage\n", sym_name, n1); returnfalse;
}
id1 = t1->type;
id2 = t2->type; goto recur; case BTF_KIND_VAR: /* extern and global linkages are compatible */
is_static1 = btf_var(t1)->linkage == BTF_VAR_STATIC;
is_static2 = btf_var(t2)->linkage == BTF_VAR_STATIC; if (is_static1 != is_static2) {
pr_warn("global '%s': incompatible var '%s' linkage\n", sym_name, n1); returnfalse;
}
id1 = t1->type;
id2 = t2->type; goto recur; case BTF_KIND_STRUCT: case BTF_KIND_UNION: { conststruct btf_member *m1, *m2;
if (!exact) returntrue;
if (btf_vlen(t1) != btf_vlen(t2)) {
pr_warn("global '%s': incompatible number of %s fields %u and %u\n",
sym_name, btf_kind_str(t1), btf_vlen(t1), btf_vlen(t2)); returnfalse;
}
n = btf_vlen(t1);
m1 = btf_members(t1);
m2 = btf_members(t2); for (i = 0; i < n; i++, m1++, m2++) {
n1 = btf__str_by_offset(btf1, m1->name_off);
n2 = btf__str_by_offset(btf2, m2->name_off); if (strcmp(n1, n2) != 0) {
pr_warn("global '%s': incompatible field #%d names '%s' and '%s'\n",
sym_name, i, n1, n2); returnfalse;
} if (m1->offset != m2->offset) {
pr_warn("global '%s': incompatible field #%d ('%s') offsets\n",
sym_name, i, n1); returnfalse;
} if (!glob_sym_btf_matches(sym_name, exact, btf1, m1->type, btf2, m2->type)) returnfalse;
}
returntrue;
} case BTF_KIND_FUNC_PROTO: { conststruct btf_param *m1, *m2;
if (btf_vlen(t1) != btf_vlen(t2)) {
pr_warn("global '%s': incompatible number of %s params %u and %u\n",
sym_name, btf_kind_str(t1), btf_vlen(t1), btf_vlen(t2)); returnfalse;
}
n = btf_vlen(t1);
m1 = btf_params(t1);
m2 = btf_params(t2); for (i = 0; i < n; i++, m1++, m2++) { /* ignore func arg names */ if (!glob_sym_btf_matches(sym_name, exact, btf1, m1->type, btf2, m2->type)) returnfalse;
}
/* now check return type as well */
id1 = t1->type;
id2 = t2->type; goto recur;
}
/* skip_mods_and_typedefs() make this impossible */ case BTF_KIND_TYPEDEF: case BTF_KIND_VOLATILE: case BTF_KIND_CONST: case BTF_KIND_RESTRICT: /* DATASECs are never compared with each other */ case BTF_KIND_DATASEC: default:
pr_warn("global '%s': unsupported BTF kind %s\n",
sym_name, btf_kind_str(t1)); returnfalse;
}
}
/* re-parse existing map definition */
t = btf__type_by_id(linker->btf, glob_sym->btf_id);
t = skip_mods_and_typedefs(linker->btf, t->type, NULL);
err = parse_btf_map_def(sym_name, linker->btf, t, true/*strict*/, &dst_def, &dst_inner_def); if (err) { /* this should not happen, because we already validated it */
pr_warn("global '%s': invalid dst map definition\n", sym_name); returnfalse;
}
/* Currently extern map definition has to be complete and match * concrete map definition exactly. This restriction might be lifted * in the future.
*/ return map_defs_match(sym_name, linker->btf, &dst_def, &dst_inner_def,
obj->btf, &src_def, &src_inner_def);
}
/* if we are dealing with externs, BTF types describing both global * and extern VARs/FUNCs should be completely present in all files
*/ if (!glob_sym->btf_id || !btf_id) {
pr_warn("BTF info is missing for global symbol '%s'\n", sym_name); returnfalse;
}
src_t = btf__type_by_id(obj->btf, btf_id); if (!btf_is_var(src_t) && !btf_is_func(src_t)) {
pr_warn("only extern variables and functions are supported, but got '%s' for '%s'\n",
btf_kind_str(src_t), sym_name); returnfalse;
}
staticint find_glob_sym_btf(struct src_obj *obj, Elf64_Sym *sym, constchar *sym_name, int *out_btf_sec_id, int *out_btf_id)
{ int i, j, n, m, btf_id = 0; conststruct btf_type *t; conststruct btf_var_secinfo *vi; constchar *name;
if (!obj->btf) {
pr_warn("failed to find BTF info for object '%s'\n", obj->filename); return -EINVAL;
}
n = btf__type_cnt(obj->btf); for (i = 1; i < n; i++) {
t = btf__type_by_id(obj->btf, i);
/* some global and extern FUNCs and VARs might not be associated with any * DATASEC, so try to detect them in the same pass
*/ if (btf_is_non_static(t)) {
name = btf__str_by_offset(obj->btf, t->name_off); if (strcmp(name, sym_name) != 0) continue;
/* remember and still try to find DATASEC */
btf_id = i; continue;
}
if (!btf_is_datasec(t)) continue;
vi = btf_var_secinfos(t); for (j = 0, m = btf_vlen(t); j < m; j++, vi++) {
t = btf__type_by_id(obj->btf, vi->type);
name = btf__str_by_offset(obj->btf, t->name_off);
if (strcmp(name, sym_name) != 0) continue; if (btf_is_var(t) && btf_var(t)->linkage == BTF_VAR_STATIC) continue; if (btf_is_func(t) && btf_func_linkage(t) == BTF_FUNC_STATIC) continue;
if (btf_id && btf_id != vi->type) {
pr_warn("global/extern '%s' BTF is ambiguous: both types #%d and #%u match\n",
sym_name, btf_id, vi->type); return -EINVAL;
}
*out_btf_sec_id = i;
*out_btf_id = vi->type;
return 0;
}
}
/* free-floating extern or global FUNC */ if (btf_id) {
*out_btf_sec_id = 0;
*out_btf_id = btf_id; return 0;
}
pr_warn("failed to find BTF info for global/extern symbol '%s'\n", sym_name); return -ENOENT;
}
for (i = 1; i < obj->sec_cnt; i++) {
sec = &obj->secs[i];
if (strcmp(sec->sec_name, sec_name) == 0) return sec;
}
return NULL;
}
staticint complete_extern_btf_info(struct btf *dst_btf, int dst_id, struct btf *src_btf, int src_id)
{ struct btf_type *dst_t = btf_type_by_id(dst_btf, dst_id); struct btf_type *src_t = btf_type_by_id(src_btf, src_id); struct btf_param *src_p, *dst_p; constchar *s; int i, n, off;
/* We already made sure that source and destination types (FUNC or * VAR) match in terms of types and argument names.
*/ if (btf_is_var(dst_t)) {
btf_var(dst_t)->linkage = BTF_VAR_GLOBAL_ALLOCATED; return 0;
}
/* Fill in all the argument names, which for extern FUNCs are missing. * We'll end up with two copies of FUNCs/VARs for externs, but that * will be taken care of by BTF dedup at the very end. * It might be that BTF types for extern in one file has less/more BTF * information (e.g., FWD instead of full STRUCT/UNION information), * but that should be (in most cases, subject to BTF dedup rules) * handled and resolved by BTF dedup algorithm as well, so we won't * worry about it. Our only job is to make sure that argument names * are populated on both sides, otherwise BTF dedup will pedantically * consider them different.
*/
src_p = btf_params(src_t);
dst_p = btf_params(dst_t); for (i = 0, n = btf_vlen(dst_t); i < n; i++, src_p++, dst_p++) { if (!src_p->name_off) continue;
/* src_btf has more complete info, so add name to dst_btf */
s = btf__str_by_offset(src_btf, src_p->name_off);
off = btf__add_str(dst_btf, s); if (off < 0) return off;
dst_p->name_off = off;
} return 0;
}
staticvoid sym_update_visibility(Elf64_Sym *sym, int sym_vis)
{ /* libelf doesn't provide setters for ST_VISIBILITY, * but it is stored in the lower 2 bits of st_other
*/
sym->st_other &= ~0x03;
sym->st_other |= sym_vis;
}
if (sym_is_extern) { if (!obj->btf) {
pr_warn("externs without BTF info are not supported\n"); return -ENOTSUP;
}
} elseif (sym->st_shndx < SHN_LORESERVE) {
src_sec = &obj->secs[sym->st_shndx]; if (src_sec->skipped) return 0;
dst_sec = &linker->secs[src_sec->dst_id];
/* allow only one STT_SECTION symbol per section */ if (sym_type == STT_SECTION && dst_sec->sec_sym_idx) {
obj->sym_map[src_sym_idx] = dst_sec->sec_sym_idx; return 0;
}
}
if (sym_bind == STB_LOCAL) goto add_sym;
/* find matching BTF info */
err = find_glob_sym_btf(obj, sym, sym_name, &btf_sec_id, &btf_id); if (err) return err;
t = btf__type_by_id(obj->btf, btf_sec_id);
sec_name = btf__str_by_offset(obj->btf, t->name_off);
/* Clang puts unannotated extern vars into * '.extern' BTF DATASEC. Treat them the same * as unannotated extern funcs (which are * currently not put into any DATASECs). * Those don't have associated src_sec/dst_sec.
*/ if (strcmp(sec_name, BTF_EXTERN_SEC) != 0) {
src_sec = find_src_sec_by_name(obj, sec_name); if (!src_sec) {
pr_warn("failed to find matching ELF sec '%s'\n", sec_name); return -ENOENT;
}
dst_sec = &linker->secs[src_sec->dst_id];
}
}
glob_sym = find_glob_sym(linker, sym_name); if (glob_sym) { /* Preventively resolve to existing symbol. This is * needed for further relocation symbol remapping in * the next step of linking.
*/
obj->sym_map[src_sym_idx] = glob_sym->sym_idx;
/* If both symbols are non-externs, at least one of * them has to be STB_WEAK, otherwise they are in * a conflict with each other.
*/ if (!sym_is_extern && !glob_sym->is_extern
&& !glob_sym->is_weak && sym_bind != STB_WEAK) {
pr_warn("conflicting non-weak symbol #%d (%s) definition in '%s'\n",
src_sym_idx, sym_name, obj->filename); return -EINVAL;
}
if (!glob_syms_match(sym_name, linker, glob_sym, obj, sym, src_sym_idx, btf_id)) return -EINVAL;
/* If new symbol is strong, then force dst_sym to be strong as * well; this way a mix of weak and non-weak extern * definitions will end up being strong.
*/ if (sym_bind == STB_GLOBAL) { /* We still need to preserve type (NOTYPE or * OBJECT/FUNC, depending on whether the symbol is * extern or not)
*/
sym_update_bind(dst_sym, STB_GLOBAL);
glob_sym->is_weak = false;
}
/* Non-default visibility is "contaminating", with stricter * visibility overwriting more permissive ones, even if more * permissive visibility comes from just an extern definition. * Currently only STV_DEFAULT and STV_HIDDEN are allowed and * ensured by ELF symbol sanity checks above.
*/ if (sym_vis > ELF64_ST_VISIBILITY(dst_sym->st_other))
sym_update_visibility(dst_sym, sym_vis);
/* If the new symbol is extern, then regardless if * existing symbol is extern or resolved global, just * keep the existing one untouched.
*/ if (sym_is_extern) return 0;
/* If existing symbol is a strong resolved symbol, bail out, * because we lost resolution battle have nothing to * contribute. We already checked above that there is no * strong-strong conflict. We also already tightened binding * and visibility, so nothing else to contribute at that point.
*/ if (!glob_sym->is_extern && sym_bind == STB_WEAK) return 0;
/* At this point, new symbol is strong non-extern, * so overwrite glob_sym with new symbol information. * Preserve binding and visibility.
*/
sym_update_type(dst_sym, sym_type);
dst_sym->st_shndx = dst_sec->sec_idx;
dst_sym->st_value = src_sec->dst_off + sym->st_value;
dst_sym->st_size = sym->st_size;
/* see comment below about dst_sec->id vs dst_sec->sec_idx */
glob_sym->sec_id = dst_sec->id;
glob_sym->is_extern = false;
if (complete_extern_btf_info(linker->btf, glob_sym->btf_id,
obj->btf, btf_id)) return -EINVAL;
/* request updating VAR's/FUNC's underlying BTF type when appending BTF type */
glob_sym->underlying_btf_id = 0;
if (sym_bind != STB_LOCAL) {
glob_sym = add_glob_sym(linker); if (!glob_sym) return -ENOMEM;
glob_sym->sym_idx = dst_sym_idx; /* we use dst_sec->id (and not dst_sec->sec_idx), because * ephemeral sections (.kconfig, .ksyms, etc) don't have * sec_idx (as they don't have corresponding ELF section), but * still have id. .extern doesn't have even ephemeral section * associated with it, so dst_sec->id == dst_sec->sec_idx == 0.
*/
glob_sym->sec_id = dst_sec ? dst_sec->id : 0;
glob_sym->name_off = name_off; /* we will fill btf_id in during BTF merging step */
glob_sym->btf_id = 0;
glob_sym->is_extern = sym_is_extern;
glob_sym->is_weak = sym_bind == STB_WEAK;
}
return 0;
}
staticint linker_append_elf_relos(struct bpf_linker *linker, struct src_obj *obj)
{ struct src_sec *src_symtab = &obj->secs[obj->symtab_sec_idx]; int i, err;
for (i = 1; i < obj->sec_cnt; i++) { struct src_sec *src_sec, *src_linked_sec; struct dst_sec *dst_sec, *dst_linked_sec;
Elf64_Rel *src_rel, *dst_rel; int j, n;
src_sec = &obj->secs[i]; if (!is_relo_sec(src_sec)) continue;
/* shdr->sh_info points to relocatable section */
src_linked_sec = &obj->secs[src_sec->shdr->sh_info]; if (src_linked_sec->skipped) continue;
dst_sec = find_dst_sec_by_name(linker, src_sec->sec_name); if (!dst_sec) {
dst_sec = add_dst_sec(linker, src_sec->sec_name); if (!dst_sec) return -ENOMEM;
err = init_sec(linker, dst_sec, src_sec); if (err) {
pr_warn("failed to init section '%s'\n", src_sec->sec_name); return err;
}
} elseif (!secs_match(dst_sec, src_sec)) {
pr_warn("sections %s are not compatible\n", src_sec->sec_name); return -EINVAL;
}
/* shdr->sh_link points to SYMTAB */
dst_sec->shdr->sh_link = linker->symtab_sec_idx;
if (src_linked_sec->shdr->sh_flags & SHF_EXECINSTR) { /* calls to the very first static function inside * .text section at offset 0 will * reference section symbol, not the * function symbol. Fix that up, * otherwise it won't be possible to * relocate calls to two different * static functions with the same name * (rom two different object files)
*/
insn = dst_linked_sec->raw_data + dst_rel->r_offset; if (insn->code == (BPF_JMP | BPF_CALL))
insn->imm += sec->dst_off / sizeof(struct bpf_insn); else
insn->imm += sec->dst_off;
} else {
pr_warn("relocation against STT_SECTION in non-exec section is not supported!\n"); return -EINVAL;
}
}
}
}
return 0;
}
static Elf64_Sym *find_sym_by_name(struct src_obj *obj, size_t sec_idx, int sym_type, constchar *sym_name)
{ struct src_sec *symtab = &obj->secs[obj->symtab_sec_idx];
Elf64_Sym *sym = symtab->data->d_buf; int i, n = symtab->shdr->sh_size / symtab->shdr->sh_entsize; int str_sec_idx = symtab->shdr->sh_link; constchar *name;
for (i = 0; i < n; i++, sym++) { if (sym->st_shndx != sec_idx) continue; if (ELF64_ST_TYPE(sym->st_info) != sym_type) continue;
name = elf_strptr(obj->elf, str_sec_idx, sym->st_name); if (!name) return NULL;
if (strcmp(sym_name, name) != 0) continue;
return sym;
}
return NULL;
}
staticint linker_fixup_btf(struct src_obj *obj)
{ constchar *sec_name; struct src_sec *sec; int i, j, n, m;
if (!obj->btf) return 0;
n = btf__type_cnt(obj->btf); for (i = 1; i < n; i++) { struct btf_var_secinfo *vi; struct btf_type *t;
t = btf_type_by_id(obj->btf, i); if (btf_kind(t) != BTF_KIND_DATASEC) continue;
sec_name = btf__str_by_offset(obj->btf, t->name_off);
sec = find_src_sec_by_name(obj, sec_name); if (sec) { /* record actual section size, unless ephemeral */ if (sec->shdr)
t->size = sec->shdr->sh_size;
} else { /* BTF can have some sections that are not represented * in ELF, e.g., .kconfig, .ksyms, .extern, which are used * for special extern variables. * * For all but one such special (ephemeral) * sections, we pre-create "section shells" to be able * to keep track of extra per-section metadata later * (e.g., those BTF extern variables). * * .extern is even more special, though, because it * contains extern variables that need to be resolved * by static linker, not libbpf and kernel. When such * externs are resolved, we are going to remove them * from .extern BTF section and might end up not * needing it at all. Each resolved extern should have * matching non-extern VAR/FUNC in other sections. * * We do support leaving some of the externs * unresolved, though, to support cases of building * libraries, which will later be linked against final * BPF applications. So if at finalization we still * see unresolved externs, we'll create .extern * section on our own.
*/ if (strcmp(sec_name, BTF_EXTERN_SEC) == 0) continue;
sec = add_src_sec(obj, sec_name); if (!sec) return -ENOMEM;
sec->ephemeral = true;
sec->sec_idx = 0; /* will match UNDEF shndx in ELF */
}
/* remember ELF section and its BTF type ID match */
sec->sec_type_id = i;
/* fix up variable offsets */
vi = btf_var_secinfos(t); for (j = 0, m = btf_vlen(t); j < m; j++, vi++) { conststruct btf_type *vt = btf__type_by_id(obj->btf, vi->type); constchar *var_name; int var_linkage;
Elf64_Sym *sym;
/* could be a variable or function */ if (!btf_is_var(vt)) continue;
/* no need to patch up static or extern vars */ if (var_linkage != BTF_VAR_GLOBAL_ALLOCATED) continue;
sym = find_sym_by_name(obj, sec->sec_idx, STT_OBJECT, var_name); if (!sym) {
pr_warn("failed to find symbol for variable '%s' in section '%s'\n", var_name, sec_name); return -ENOENT;
}
vi->offset = sym->st_value;
}
}
return 0;
}
staticint linker_append_btf(struct bpf_linker *linker, struct src_obj *obj)
{ conststruct btf_type *t; int i, j, n, start_id, id, err; constchar *name;
if (!obj->btf) return 0;
start_id = btf__type_cnt(linker->btf);
n = btf__type_cnt(obj->btf);
obj->btf_type_map = calloc(n + 1, sizeof(int)); if (!obj->btf_type_map) return -ENOMEM;
for (i = 1; i < n; i++) { struct glob_sym *glob_sym = NULL;
t = btf__type_by_id(obj->btf, i);
/* DATASECs are handled specially below */ if (btf_kind(t) == BTF_KIND_DATASEC) continue;
if (btf_is_non_static(t)) { /* there should be glob_sym already */
name = btf__str_by_offset(obj->btf, t->name_off);
glob_sym = find_glob_sym(linker, name);
/* VARs without corresponding glob_sym are those that * belong to skipped/deduplicated sections (i.e., * license and version), so just skip them
*/ if (!glob_sym) continue;
/* linker_append_elf_sym() might have requested * updating underlying type ID, if extern was resolved
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5
¤ Dauer der Verarbeitung: 0.25 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.