text: /*
   * 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 <stdarg.h>
  #include <string.h>
  #include "linux/btf.h"
  #include "libbpf.h"
  #include "bcc_libbpf_inc.h"
  #include <vector>
  #include <byteswap.h>
  
  #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, &param);
  }
  
  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, &param);
  }
  
  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, &param);
  }
  
  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 &sections) : 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<std::string, std::string> &remapped_sources,
                   uint8_t **new_btf_sec, uintptr_t *new_btf_sec_size) {
  
    // Line cache for remapped files
    std::map<std::string, std::vector<std::string>> LineCaches;
    for (auto it = remapped_sources.begin(); it != remapped_sources.end(); ++it) {
      size_t FileBufSize = it->second.size();
      std::vector<std::string> 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<std::string> &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<std::string, std::string> &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
  