/* * Copyright (c) 2019 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "bcc_btf.h" #include #include #include "linux/btf.h" #include "libbpf.h" #include "bcc_libbpf_inc.h" #include #include #define BCC_MAX_ERRNO 4095 #define BCC_IS_ERR_VALUE(x) ((x) >= (unsigned long)-BCC_MAX_ERRNO) #define BCC_IS_ERR(ptr) BCC_IS_ERR_VALUE((unsigned long)ptr) #ifndef offsetofend # define offsetofend(TYPE, FIELD) \ (offsetof(TYPE, FIELD) + sizeof(((TYPE *)0)->FIELD)) #endif namespace btf_ext_vendored { /* The minimum bpf_func_info checked by the loader */ struct bpf_func_info_min { uint32_t insn_off; uint32_t type_id; }; /* The minimum bpf_line_info checked by the loader */ struct bpf_line_info_min { uint32_t insn_off; uint32_t file_name_off; uint32_t line_off; uint32_t line_col; }; struct btf_ext_sec_setup_param { uint32_t off; uint32_t len; uint32_t min_rec_size; struct btf_ext_info *ext_info; const char *desc; }; static int btf_ext_setup_info(struct btf_ext *btf_ext, struct btf_ext_sec_setup_param *ext_sec) { const struct btf_ext_info_sec *sinfo; struct btf_ext_info *ext_info; uint32_t info_left, record_size; /* The start of the info sec (including the __u32 record_size). */ void *info; if (ext_sec->len == 0) return 0; if (ext_sec->off & 0x03) { /*pr_debug(".BTF.ext %s section is not aligned to 4 bytes\n", ext_sec->desc);*/ return -EINVAL; } info = (uint8_t*)btf_ext->data + btf_ext->hdr->hdr_len + ext_sec->off; info_left = ext_sec->len; if ((uint8_t*)btf_ext->data + btf_ext->data_size < (uint8_t*)info + ext_sec->len) { /*pr_debug("%s section (off:%u len:%u) is beyond the end of the ELF section .BTF.ext\n", ext_sec->desc, ext_sec->off, ext_sec->len);*/ return -EINVAL; } /* At least a record size */ if (info_left < sizeof(uint32_t)) { /*pr_debug(".BTF.ext %s record size not found\n", ext_sec->desc);*/ return -EINVAL; } /* The record size needs to meet the minimum standard */ record_size = *(uint32_t *)info; if (record_size < ext_sec->min_rec_size || record_size & 0x03) { /*pr_debug("%s section in .BTF.ext has invalid record size %u\n", ext_sec->desc, record_size);*/ return -EINVAL; } sinfo = (struct btf_ext_info_sec*)((uint8_t*)info + sizeof(uint32_t)); info_left -= sizeof(uint32_t); /* If no records, return failure now so .BTF.ext won't be used. */ if (!info_left) { /*pr_debug("%s section in .BTF.ext has no records", ext_sec->desc);*/ return -EINVAL; } while (info_left) { unsigned int sec_hdrlen = sizeof(struct btf_ext_info_sec); uint64_t total_record_size; uint32_t num_records; if (info_left < sec_hdrlen) { /*pr_debug("%s section header is not found in .BTF.ext\n", ext_sec->desc);*/ return -EINVAL; } num_records = sinfo->num_info; if (num_records == 0) { /*pr_debug("%s section has incorrect num_records in .BTF.ext\n", ext_sec->desc);*/ return -EINVAL; } total_record_size = sec_hdrlen + (uint64_t)num_records * record_size; if (info_left < total_record_size) { /*pr_debug("%s section has incorrect num_records in .BTF.ext\n", ext_sec->desc);*/ return -EINVAL; } info_left -= total_record_size; sinfo = (struct btf_ext_info_sec *)((uint8_t*)sinfo + total_record_size); } ext_info = ext_sec->ext_info; ext_info->len = ext_sec->len - sizeof(uint32_t); ext_info->rec_size = record_size; ext_info->info = (uint8_t*)info + sizeof(uint32_t); return 0; } static int btf_ext_setup_func_info(struct btf_ext *btf_ext) { struct btf_ext_sec_setup_param param = { .off = btf_ext->hdr->func_info_off, .len = btf_ext->hdr->func_info_len, .min_rec_size = sizeof(struct bpf_func_info_min), .ext_info = &btf_ext->func_info, .desc = "func_info" }; return btf_ext_setup_info(btf_ext, ¶m); } static int btf_ext_setup_line_info(struct btf_ext *btf_ext) { struct btf_ext_sec_setup_param param = { .off = btf_ext->hdr->line_info_off, .len = btf_ext->hdr->line_info_len, .min_rec_size = sizeof(struct bpf_line_info_min), .ext_info = &btf_ext->line_info, .desc = "line_info", }; return btf_ext_setup_info(btf_ext, ¶m); } static int btf_ext_setup_core_relos(struct btf_ext *btf_ext) { struct btf_ext_sec_setup_param param = { .off = btf_ext->hdr->core_relo_off, .len = btf_ext->hdr->core_relo_len, .min_rec_size = sizeof(struct bpf_core_relo), .ext_info = &btf_ext->core_relo_info, .desc = "core_relo", }; return btf_ext_setup_info(btf_ext, ¶m); } static int btf_ext_parse_hdr(uint8_t *data, uint32_t data_size) { const struct btf_ext_header *hdr = (struct btf_ext_header *)data; if (data_size < offsetofend(struct btf_ext_header, hdr_len) || data_size < hdr->hdr_len) { //pr_debug("BTF.ext header not found"); return -EINVAL; } if (hdr->magic == bswap_16(BTF_MAGIC)) { //pr_warn("BTF.ext in non-native endianness is not supported\n"); return -ENOTSUP; } else if (hdr->magic != BTF_MAGIC) { //pr_debug("Invalid BTF.ext magic:%x\n", hdr->magic); return -EINVAL; } if (hdr->version != BTF_VERSION) { //pr_debug("Unsupported BTF.ext version:%u\n", hdr->version); return -ENOTSUP; } if (hdr->flags) { //pr_debug("Unsupported BTF.ext flags:%x\n", hdr->flags); return -ENOTSUP; } if (data_size == hdr->hdr_len) { //pr_debug("BTF.ext has no data\n"); return -EINVAL; } return 0; } void btf_ext__free(struct btf_ext *btf_ext) { if((!btf_ext) || BCC_IS_ERR_VALUE((unsigned long)btf_ext)) return; free(btf_ext->data); free(btf_ext); } struct btf_ext *btf_ext__new(const uint8_t *data, uint32_t size) { struct btf_ext *btf_ext; int err; btf_ext = (struct btf_ext*)calloc(1, sizeof(struct btf_ext)); if (!btf_ext) return (struct btf_ext*)-ENOMEM; btf_ext->data_size = size; btf_ext->data = malloc(size); if (!btf_ext->data) { err = -ENOMEM; goto done; } memcpy(btf_ext->data, data, size); err = btf_ext_parse_hdr((uint8_t*)btf_ext->data, size); if (err) goto done; if (btf_ext->hdr->hdr_len < offsetofend(struct btf_ext_header, line_info_len)) { err = -EINVAL; goto done; } err = btf_ext_setup_func_info(btf_ext); if (err) goto done; err = btf_ext_setup_line_info(btf_ext); if (err) goto done; if (btf_ext->hdr->hdr_len < offsetofend(struct btf_ext_header, core_relo_len)) { err = -EINVAL; goto done; } err = btf_ext_setup_core_relos(btf_ext); if (err) goto done; done: if (err) { btf_ext__free(btf_ext); return (struct btf_ext*)(uintptr_t)err; } return btf_ext; } static int btf_ext_reloc_info(const struct btf *btf, const struct btf_ext_info *ext_info, const char *sec_name, uint32_t insns_cnt, void **info, uint32_t *cnt) { uint32_t sec_hdrlen = sizeof(struct btf_ext_info_sec); uint32_t i, record_size, existing_len, records_len; struct btf_ext_info_sec *sinfo; const char *info_sec_name; uint64_t remain_len; void *data; record_size = ext_info->rec_size; sinfo = (struct btf_ext_info_sec*)ext_info->info; remain_len = ext_info->len; while (remain_len > 0) { records_len = sinfo->num_info * record_size; info_sec_name = btf__name_by_offset(btf, sinfo->sec_name_off); if (strcmp(info_sec_name, sec_name)) { remain_len -= sec_hdrlen + records_len; sinfo = (struct btf_ext_info_sec*)((uint8_t *)sinfo + sec_hdrlen + records_len); continue; } existing_len = (*cnt) * record_size; data = realloc(*info, existing_len + records_len); if (!data) return -ENOMEM; memcpy((uint8_t*)data + existing_len, sinfo->data, records_len); /* adjust insn_off only, the rest data will be passed * to the kernel. */ for (i = 0; i < sinfo->num_info; i++) { uint32_t *insn_off; insn_off = (uint32_t *)((uint8_t*)data + existing_len + (i * record_size)); *insn_off = *insn_off / sizeof(struct bpf_insn) + insns_cnt; } *info = data; *cnt += sinfo->num_info; return 0; } return -ENOENT; } int btf_ext__reloc_func_info(const struct btf *btf, const struct btf_ext *btf_ext, const char *sec_name, uint32_t insns_cnt, void **func_info, uint32_t *cnt) { return btf_ext_vendored::btf_ext_reloc_info(btf, &btf_ext->func_info, sec_name, insns_cnt, func_info, cnt); } int btf_ext__reloc_line_info(const struct btf *btf, const struct btf_ext *btf_ext, const char *sec_name, uint32_t insns_cnt, void **line_info, uint32_t *cnt) { return btf_ext_vendored::btf_ext_reloc_info(btf, &btf_ext->line_info, sec_name, insns_cnt, line_info, cnt); } } // namespace btf_ext_vendored namespace ebpf { int32_t BTFStringTable::addString(std::string S) { // Check whether the string already exists. for (auto &OffsetM : OffsetToIdMap) { if (Table[OffsetM.second] == S) return OffsetM.first; } // Make sure we do not overflow the string table. if (OrigTblLen + Size + S.size() + 1 >= BTF_MAX_NAME_OFFSET) return -1; // Not find, add to the string table. uint32_t Offset = Size; OffsetToIdMap[Offset] = Table.size(); Table.push_back(S); Size += S.size() + 1; return Offset; } BTF::BTF(bool debug, sec_map_def §ions) : debug_(debug), btf_(nullptr), btf_ext_(nullptr), sections_(sections) { if (!debug) libbpf_set_print(NULL); } BTF::~BTF() { btf__free(btf_); btf_ext__free(btf_ext_); } void BTF::warning(const char *format, ...) { va_list args; if (!debug_) return; va_start(args, format); vfprintf(stderr, format, args); va_end(args); } void BTF::fixup_btf(uint8_t *type_sec, uintptr_t type_sec_size, char *strings) { uint8_t *next_type = type_sec; uint8_t *end_type = type_sec + type_sec_size; int base_size = sizeof(struct btf_type); while (next_type < end_type) { struct btf_type *t = (struct btf_type *)next_type; unsigned short vlen = BTF_INFO_VLEN(t->info); next_type += base_size; switch(BTF_INFO_KIND(t->info)) { case BTF_KIND_FWD: case BTF_KIND_CONST: case BTF_KIND_VOLATILE: case BTF_KIND_RESTRICT: case BTF_KIND_PTR: case BTF_KIND_TYPEDEF: break; case BTF_KIND_FUNC: // sanitize vlen to be 0 since bcc does not // care about func scope (static, global, extern) yet. t->info &= ~0xffff; break; case BTF_KIND_INT: next_type += sizeof(uint32_t); break; case BTF_KIND_ENUM: next_type += vlen * sizeof(struct btf_enum); break; case BTF_KIND_ARRAY: next_type += sizeof(struct btf_array); break; case BTF_KIND_STRUCT: case BTF_KIND_UNION: next_type += vlen * sizeof(struct btf_member); break; case BTF_KIND_FUNC_PROTO: next_type += vlen * sizeof(struct btf_param); break; case BTF_KIND_VAR: { // BTF_KIND_VAR is not used by bcc, so // a sanitization to convert it to an int. // One extra __u32 after btf_type. if (sizeof(struct btf_var) == 4) { t->name_off = 0; t->info = BTF_KIND_INT << 24; t->size = 4; unsigned *intp = (unsigned *)next_type; *intp = BTF_INT_BITS(t->size << 3); } next_type += sizeof(struct btf_var); break; } case BTF_KIND_DATASEC: { // bcc does not use BTF_KIND_DATASEC, so // a sanitization here to convert it to a list // of void pointers. // btf_var_secinfo is 3 __u32's for each var. if (sizeof(struct btf_var_secinfo) == 12) { t->name_off = 0; t->info = BTF_KIND_PTR << 24; t->type = 0; struct btf_type *typep = (struct btf_type *)next_type; for (int i = 0; i < vlen; i++) { typep->name_off = 0; typep->info = BTF_KIND_PTR << 24; typep->type = 0; typep++; } } next_type += vlen * sizeof(struct btf_var_secinfo); break; } default: // Something not understood return; } } } // The compiler doesn't have source code for remapped files. // So we modify .BTF and .BTF.ext sections here to add these // missing line source codes. // The .BTF and .BTF.ext ELF section specification can be // found at linux repo: linux/Documentation/bpf/btf.rst. void BTF::adjust(uint8_t *btf_sec, uintptr_t btf_sec_size, uint8_t *btf_ext_sec, uintptr_t btf_ext_sec_size, std::map &remapped_sources, uint8_t **new_btf_sec, uintptr_t *new_btf_sec_size) { // Line cache for remapped files std::map> LineCaches; for (auto it = remapped_sources.begin(); it != remapped_sources.end(); ++it) { size_t FileBufSize = it->second.size(); std::vector LineCache; for (uint32_t start = 0, end = start; end < FileBufSize; end++) { if (it->second[end] == '\n' || end == FileBufSize - 1 || (it->second[end] == '\r' && it->second[end + 1] == '\n')) { // Not including the endline LineCache.push_back(std::string(it->second.substr(start, end - start))); if (it->second[end] == '\r') end++; start = end + 1; } } LineCaches[it->first] = std::move(LineCache); } struct btf_header *hdr = (struct btf_header *)btf_sec; struct btf_ext_vendored::btf_ext_header *ehdr = (struct btf_ext_vendored::btf_ext_header *)btf_ext_sec; // Fixup btf for old kernels or kernel requirements. fixup_btf(btf_sec + hdr->hdr_len + hdr->type_off, hdr->type_len, (char *)(btf_sec + hdr->hdr_len + hdr->str_off)); // Check the LineInfo table and add missing lines char *strings = (char *)(btf_sec + hdr->hdr_len + hdr->str_off); unsigned orig_strings_len = hdr->str_len; unsigned *linfo_s = (unsigned *)(btf_ext_sec + ehdr->hdr_len + ehdr->line_info_off); unsigned lrec_size = *linfo_s; linfo_s++; unsigned linfo_len = ehdr->line_info_len - 4; // Go through all line info. For any line number whose line is in the LineCaches, // Correct the line_off and record the corresponding source line in BTFStringTable, // which later will be merged into .BTF string section. BTFStringTable new_strings(orig_strings_len); bool overflow = false; while (!overflow && linfo_len) { unsigned num_recs = linfo_s[1]; linfo_s += 2; for (unsigned i = 0; !overflow && i < num_recs; i++) { struct bpf_line_info *linfo = (struct bpf_line_info *)linfo_s; if (linfo->line_off == 0) { for (auto it = LineCaches.begin(); it != LineCaches.end(); ++it) { if (strcmp(strings + linfo->file_name_off, it->first.c_str()) == 0) { unsigned line_num = BPF_LINE_INFO_LINE_NUM(linfo->line_col); if (line_num > 0 && line_num <= it->second.size()) { int offset = new_strings.addString(it->second[line_num - 1]); if (offset < 0) { overflow = true; warning(".BTF string table overflowed, some lines missing\n"); break; } linfo->line_off = orig_strings_len + offset; } } } } linfo_s += lrec_size >> 2; } linfo_len -= 8 + num_recs * lrec_size; } // If any new source lines need to be recorded, do not touch the original section, // allocate a new section. The original section is allocated through llvm infra. if (new_strings.getSize() > 0) { // LLVM generated .BTF layout always has type_sec followed by str_sec without holes, // so we can just append the new strings to the end and adjust str_sec size. unsigned tmp_sec_size = btf_sec_size + new_strings.getSize(); uint8_t *tmp_sec = new uint8_t[tmp_sec_size]; memcpy(tmp_sec, btf_sec, btf_sec_size); struct btf_header *nhdr = (struct btf_header *)tmp_sec; nhdr->str_len += new_strings.getSize(); // Populate new strings to the string table. uint8_t *new_str = tmp_sec + nhdr->hdr_len + nhdr->str_off + orig_strings_len; std::vector &Table = new_strings.getTable(); for (unsigned i = 0; i < Table.size(); i++) { strcpy((char *)new_str, Table[i].c_str()); new_str += Table[i].size() + 1; } *new_btf_sec = tmp_sec; *new_btf_sec_size = tmp_sec_size; } } int BTF::load(uint8_t *btf_sec, uintptr_t btf_sec_size, uint8_t *btf_ext_sec, uintptr_t btf_ext_sec_size, std::map &remapped_sources) { struct btf *btf; struct btf_ext_vendored::btf_ext *btf_ext; uint8_t *new_btf_sec = NULL; uintptr_t new_btf_sec_size = 0; adjust(btf_sec, btf_sec_size, btf_ext_sec, btf_ext_sec_size, remapped_sources, &new_btf_sec, &new_btf_sec_size); if (new_btf_sec) { btf = btf__new(new_btf_sec, new_btf_sec_size); delete[] new_btf_sec; } else { btf = btf__new(btf_sec, btf_sec_size); } if (BCC_IS_ERR(btf)) { warning("Processing .BTF section failed\n"); return -1; } if (btf__load_into_kernel(btf)) { btf__free(btf); warning("Loading .BTF section failed\n"); return -1; } btf_ext = btf_ext_vendored::btf_ext__new(btf_ext_sec, btf_ext_sec_size); if (BCC_IS_ERR(btf_ext)) { btf__free(btf); warning("Processing .BTF.ext section failed\n"); return -1; } btf_ = btf; btf_ext_ = btf_ext; return 0; } int BTF::get_fd() { return btf__fd(btf_); } int BTF::get_btf_info(const char *fname, void **func_info, unsigned *func_info_cnt, unsigned *finfo_rec_size, void **line_info, unsigned *line_info_cnt, unsigned *linfo_rec_size) { int ret; *func_info = *line_info = NULL; *func_info_cnt = *line_info_cnt = 0; *finfo_rec_size = btf_ext_->func_info.rec_size; *linfo_rec_size = btf_ext_->line_info.rec_size; ret = btf_ext_vendored::btf_ext__reloc_func_info(btf_, btf_ext_, fname, 0, func_info, func_info_cnt); if (ret) { warning(".BTF.ext reloc func_info failed\n"); return ret; } ret = btf_ext_vendored::btf_ext__reloc_line_info(btf_, btf_ext_, fname, 0, line_info, line_info_cnt); if (ret) { warning(".BTF.ext reloc line_info failed\n"); return ret; } return 0; } int BTF::get_map_tids(std::string map_name, unsigned expected_ksize, unsigned expected_vsize, unsigned *key_tid, unsigned *value_tid) { auto struct_name = "____btf_map_" + map_name; auto type_id = btf__find_by_name_kind(btf_, struct_name.c_str(), BTF_KIND_STRUCT); if (type_id < 0) { warning("struct %s not found in BTF\n", struct_name.c_str()); return -1; } auto struct_type = btf__type_by_id(btf_, type_id); if (!struct_type || btf_vlen(struct_type) < 2) { warning("struct %s is not a valid map struct\n", struct_name.c_str()); return -1; } auto members = btf_members(struct_type); auto key = members[0]; auto key_name = btf__name_by_offset(btf_, key.name_off); if (strcmp(key_name, "key")) { warning("'key' should be the first member\n"); return -1; } auto key_size = btf__resolve_size(btf_, key.type); if (key_size != expected_ksize) { warning("expect key size to be %d, got %d\n", expected_ksize, key_size); return -1; } *key_tid = key.type; auto value = members[1]; auto value_name = btf__name_by_offset(btf_, value.name_off); if (strcmp(value_name, "value")) { warning("'value' should be the second member\n"); return -1; } auto value_size = btf__resolve_size(btf_, value.type); if (value_size != expected_vsize) { warning("expect value size to be %d, got %d\n", expected_vsize, value_size); return -1; } *value_tid = value.type; return 0; } } // namespace ebpf