* Copyright (C) 2015 Wang Nan <wangnan0@huawei.com>
  * Copyright (C) 2015 Huawei Inc.
  * Copyright (C) 2017 Nicira, Inc.
+ * Copyright (C) 2019 Isovalent, Inc.
  */
 
 #ifndef _GNU_SOURCE
                enum {
                        RELO_LD64,
                        RELO_CALL,
+                       RELO_DATA,
                } type;
                int insn_idx;
                union {
        __u32 line_info_cnt;
 };
 
+enum libbpf_map_type {
+       LIBBPF_MAP_UNSPEC,
+       LIBBPF_MAP_DATA,
+       LIBBPF_MAP_BSS,
+       LIBBPF_MAP_RODATA,
+};
+
+static const char * const libbpf_type_to_btf_name[] = {
+       [LIBBPF_MAP_DATA]       = ".data",
+       [LIBBPF_MAP_BSS]        = ".bss",
+       [LIBBPF_MAP_RODATA]     = ".rodata",
+};
+
 struct bpf_map {
        int fd;
        char *name;
        __u32 btf_value_type_id;
        void *priv;
        bpf_map_clear_priv_t clear_priv;
+       enum libbpf_map_type libbpf_type;
+};
+
+struct bpf_secdata {
+       void *rodata;
+       void *data;
 };
 
 static LIST_HEAD(bpf_objects_list);
 
 struct bpf_object {
+       char name[BPF_OBJ_NAME_LEN];
        char license[64];
        __u32 kern_version;
 
        size_t nr_programs;
        struct bpf_map *maps;
        size_t nr_maps;
+       struct bpf_secdata sections;
 
        bool loaded;
        bool has_pseudo_calls;
                Elf *elf;
                GElf_Ehdr ehdr;
                Elf_Data *symbols;
+               Elf_Data *data;
+               Elf_Data *rodata;
+               Elf_Data *bss;
                size_t strtabidx;
                struct {
                        GElf_Shdr shdr;
                int nr_reloc;
                int maps_shndx;
                int text_shndx;
+               int data_shndx;
+               int rodata_shndx;
+               int bss_shndx;
        } efile;
        /*
         * All loaded bpf_object is linked in a list, which is
                                          size_t obj_buf_sz)
 {
        struct bpf_object *obj;
+       char *end;
 
        obj = calloc(1, sizeof(struct bpf_object) + strlen(path) + 1);
        if (!obj) {
        }
 
        strcpy(obj->path, path);
-       obj->efile.fd = -1;
+       /* Using basename() GNU version which doesn't modify arg. */
+       strncpy(obj->name, basename((void *)path),
+               sizeof(obj->name) - 1);
+       end = strchr(obj->name, '.');
+       if (end)
+               *end = 0;
 
+       obj->efile.fd = -1;
        /*
         * Caller of this function should also calls
         * bpf_object__elf_finish() after data collection to return
        obj->efile.obj_buf = obj_buf;
        obj->efile.obj_buf_sz = obj_buf_sz;
        obj->efile.maps_shndx = -1;
+       obj->efile.data_shndx = -1;
+       obj->efile.rodata_shndx = -1;
+       obj->efile.bss_shndx = -1;
 
        obj->loaded = false;
 
                obj->efile.elf = NULL;
        }
        obj->efile.symbols = NULL;
+       obj->efile.data = NULL;
+       obj->efile.rodata = NULL;
+       obj->efile.bss = NULL;
 
        zfree(&obj->efile.reloc);
        obj->efile.nr_reloc = 0;
        return false;
 }
 
+static bool bpf_object__has_maps(const struct bpf_object *obj)
+{
+       return obj->efile.maps_shndx >= 0 ||
+              obj->efile.data_shndx >= 0 ||
+              obj->efile.rodata_shndx >= 0 ||
+              obj->efile.bss_shndx >= 0;
+}
+
+static int
+bpf_object__init_internal_map(struct bpf_object *obj, struct bpf_map *map,
+                             enum libbpf_map_type type, Elf_Data *data,
+                             void **data_buff)
+{
+       struct bpf_map_def *def = &map->def;
+       char map_name[BPF_OBJ_NAME_LEN];
+
+       map->libbpf_type = type;
+       map->offset = ~(typeof(map->offset))0;
+       snprintf(map_name, sizeof(map_name), "%.8s%.7s", obj->name,
+                libbpf_type_to_btf_name[type]);
+       map->name = strdup(map_name);
+       if (!map->name) {
+               pr_warning("failed to alloc map name\n");
+               return -ENOMEM;
+       }
+
+       def->type = BPF_MAP_TYPE_ARRAY;
+       def->key_size = sizeof(int);
+       def->value_size = data->d_size;
+       def->max_entries = 1;
+       def->map_flags = type == LIBBPF_MAP_RODATA ?
+                        BPF_F_RDONLY_PROG : 0;
+       if (data_buff) {
+               *data_buff = malloc(data->d_size);
+               if (!*data_buff) {
+                       zfree(&map->name);
+                       pr_warning("failed to alloc map content buffer\n");
+                       return -ENOMEM;
+               }
+               memcpy(*data_buff, data->d_buf, data->d_size);
+       }
+
+       pr_debug("map %ld is \"%s\"\n", map - obj->maps, map->name);
+       return 0;
+}
+
 static int
 bpf_object__init_maps(struct bpf_object *obj, int flags)
 {
+       int i, map_idx, map_def_sz, nr_syms, nr_maps = 0, nr_maps_glob = 0;
        bool strict = !(flags & MAPS_RELAX_COMPAT);
-       int i, map_idx, map_def_sz, nr_maps = 0;
-       Elf_Scn *scn;
-       Elf_Data *data = NULL;
        Elf_Data *symbols = obj->efile.symbols;
+       Elf_Data *data = NULL;
+       int ret = 0;
 
-       if (obj->efile.maps_shndx < 0)
-               return -EINVAL;
        if (!symbols)
                return -EINVAL;
+       nr_syms = symbols->d_size / sizeof(GElf_Sym);
 
-       scn = elf_getscn(obj->efile.elf, obj->efile.maps_shndx);
-       if (scn)
-               data = elf_getdata(scn, NULL);
-       if (!scn || !data) {
-               pr_warning("failed to get Elf_Data from map section %d\n",
-                          obj->efile.maps_shndx);
-               return -EINVAL;
+       if (obj->efile.maps_shndx >= 0) {
+               Elf_Scn *scn = elf_getscn(obj->efile.elf,
+                                         obj->efile.maps_shndx);
+
+               if (scn)
+                       data = elf_getdata(scn, NULL);
+               if (!scn || !data) {
+                       pr_warning("failed to get Elf_Data from map section %d\n",
+                                  obj->efile.maps_shndx);
+                       return -EINVAL;
+               }
        }
 
        /*
         *
         * TODO: Detect array of map and report error.
         */
-       for (i = 0; i < symbols->d_size / sizeof(GElf_Sym); i++) {
+       if (obj->efile.data_shndx >= 0)
+               nr_maps_glob++;
+       if (obj->efile.rodata_shndx >= 0)
+               nr_maps_glob++;
+       if (obj->efile.bss_shndx >= 0)
+               nr_maps_glob++;
+       for (i = 0; data && i < nr_syms; i++) {
                GElf_Sym sym;
 
                if (!gelf_getsym(symbols, i, &sym))
        /* Alloc obj->maps and fill nr_maps. */
        pr_debug("maps in %s: %d maps in %zd bytes\n", obj->path,
                 nr_maps, data->d_size);
-
-       if (!nr_maps)
+       if (!nr_maps && !nr_maps_glob)
                return 0;
 
        /* Assume equally sized map definitions */
-       map_def_sz = data->d_size / nr_maps;
-       if (!data->d_size || (data->d_size % nr_maps) != 0) {
-               pr_warning("unable to determine map definition size "
-                          "section %s, %d maps in %zd bytes\n",
-                          obj->path, nr_maps, data->d_size);
-               return -EINVAL;
+       if (data) {
+               map_def_sz = data->d_size / nr_maps;
+               if (!data->d_size || (data->d_size % nr_maps) != 0) {
+                       pr_warning("unable to determine map definition size "
+                                  "section %s, %d maps in %zd bytes\n",
+                                  obj->path, nr_maps, data->d_size);
+                       return -EINVAL;
+               }
        }
 
+       nr_maps += nr_maps_glob;
        obj->maps = calloc(nr_maps, sizeof(obj->maps[0]));
        if (!obj->maps) {
                pr_warning("alloc maps for object failed\n");
        /*
         * Fill obj->maps using data in "maps" section.
         */
-       for (i = 0, map_idx = 0; i < symbols->d_size / sizeof(GElf_Sym); i++) {
+       for (i = 0, map_idx = 0; data && i < nr_syms; i++) {
                GElf_Sym sym;
                const char *map_name;
                struct bpf_map_def *def;
                map_name = elf_strptr(obj->efile.elf,
                                      obj->efile.strtabidx,
                                      sym.st_name);
+
+               obj->maps[map_idx].libbpf_type = LIBBPF_MAP_UNSPEC;
                obj->maps[map_idx].offset = sym.st_value;
                if (sym.st_value + map_def_sz > data->d_size) {
                        pr_warning("corrupted maps section in %s: last map \"%s\" too small\n",
                map_idx++;
        }
 
-       qsort(obj->maps, obj->nr_maps, sizeof(obj->maps[0]), compare_bpf_map);
-       return 0;
+       /*
+        * Populate rest of obj->maps with libbpf internal maps.
+        */
+       if (obj->efile.data_shndx >= 0)
+               ret = bpf_object__init_internal_map(obj, &obj->maps[map_idx++],
+                                                   LIBBPF_MAP_DATA,
+                                                   obj->efile.data,
+                                                   &obj->sections.data);
+       if (!ret && obj->efile.rodata_shndx >= 0)
+               ret = bpf_object__init_internal_map(obj, &obj->maps[map_idx++],
+                                                   LIBBPF_MAP_RODATA,
+                                                   obj->efile.rodata,
+                                                   &obj->sections.rodata);
+       if (!ret && obj->efile.bss_shndx >= 0)
+               ret = bpf_object__init_internal_map(obj, &obj->maps[map_idx++],
+                                                   LIBBPF_MAP_BSS,
+                                                   obj->efile.bss, NULL);
+       if (!ret)
+               qsort(obj->maps, obj->nr_maps, sizeof(obj->maps[0]),
+                     compare_bpf_map);
+       return ret;
 }
 
 static bool section_have_execinstr(struct bpf_object *obj, int idx)
                                        pr_warning("failed to alloc program %s (%s): %s",
                                                   name, obj->path, cp);
                                }
+                       } else if (strcmp(name, ".data") == 0) {
+                               obj->efile.data = data;
+                               obj->efile.data_shndx = idx;
+                       } else if (strcmp(name, ".rodata") == 0) {
+                               obj->efile.rodata = data;
+                               obj->efile.rodata_shndx = idx;
+                       } else {
+                               pr_debug("skip section(%d) %s\n", idx, name);
                        }
                } else if (sh.sh_type == SHT_REL) {
                        void *reloc = obj->efile.reloc;
                                obj->efile.reloc[n].shdr = sh;
                                obj->efile.reloc[n].data = data;
                        }
+               } else if (sh.sh_type == SHT_NOBITS && strcmp(name, ".bss") == 0) {
+                       obj->efile.bss = data;
+                       obj->efile.bss_shndx = idx;
                } else {
                        pr_debug("skip section(%d) %s\n", idx, name);
                }
                        }
                }
        }
-       if (obj->efile.maps_shndx >= 0) {
+       if (bpf_object__has_maps(obj)) {
                err = bpf_object__init_maps(obj, flags);
                if (err)
                        goto out;
        return NULL;
 }
 
+static bool bpf_object__shndx_is_data(const struct bpf_object *obj,
+                                     int shndx)
+{
+       return shndx == obj->efile.data_shndx ||
+              shndx == obj->efile.bss_shndx ||
+              shndx == obj->efile.rodata_shndx;
+}
+
+static bool bpf_object__shndx_is_maps(const struct bpf_object *obj,
+                                     int shndx)
+{
+       return shndx == obj->efile.maps_shndx;
+}
+
+static bool bpf_object__relo_in_known_section(const struct bpf_object *obj,
+                                             int shndx)
+{
+       return shndx == obj->efile.text_shndx ||
+              bpf_object__shndx_is_maps(obj, shndx) ||
+              bpf_object__shndx_is_data(obj, shndx);
+}
+
+static enum libbpf_map_type
+bpf_object__section_to_libbpf_map_type(const struct bpf_object *obj, int shndx)
+{
+       if (shndx == obj->efile.data_shndx)
+               return LIBBPF_MAP_DATA;
+       else if (shndx == obj->efile.bss_shndx)
+               return LIBBPF_MAP_BSS;
+       else if (shndx == obj->efile.rodata_shndx)
+               return LIBBPF_MAP_RODATA;
+       else
+               return LIBBPF_MAP_UNSPEC;
+}
+
 static int
 bpf_program__collect_reloc(struct bpf_program *prog, GElf_Shdr *shdr,
                           Elf_Data *data, struct bpf_object *obj)
 {
        Elf_Data *symbols = obj->efile.symbols;
-       int text_shndx = obj->efile.text_shndx;
-       int maps_shndx = obj->efile.maps_shndx;
        struct bpf_map *maps = obj->maps;
        size_t nr_maps = obj->nr_maps;
        int i, nrels;
                GElf_Sym sym;
                GElf_Rel rel;
                unsigned int insn_idx;
+               unsigned int shdr_idx;
                struct bpf_insn *insns = prog->insns;
+               enum libbpf_map_type type;
+               const char *name;
                size_t map_idx;
 
                if (!gelf_getrel(data, i, &rel)) {
                                   GELF_R_SYM(rel.r_info));
                        return -LIBBPF_ERRNO__FORMAT;
                }
-               pr_debug("relo for %lld value %lld name %d\n",
+
+               name = elf_strptr(obj->efile.elf, obj->efile.strtabidx,
+                                 sym.st_name) ? : "<?>";
+
+               pr_debug("relo for %lld value %lld name %d (\'%s\')\n",
                         (long long) (rel.r_info >> 32),
-                        (long long) sym.st_value, sym.st_name);
+                        (long long) sym.st_value, sym.st_name, name);
 
-               if (sym.st_shndx != maps_shndx && sym.st_shndx != text_shndx) {
-                       pr_warning("Program '%s' contains non-map related relo data pointing to section %u\n",
-                                  prog->section_name, sym.st_shndx);
+               shdr_idx = sym.st_shndx;
+               if (!bpf_object__relo_in_known_section(obj, shdr_idx)) {
+                       pr_warning("Program '%s' contains unrecognized relo data pointing to section %u\n",
+                                  prog->section_name, shdr_idx);
                        return -LIBBPF_ERRNO__RELOC;
                }
 
                        return -LIBBPF_ERRNO__RELOC;
                }
 
-               if (sym.st_shndx == maps_shndx) {
-                       /* TODO: 'maps' is sorted. We can use bsearch to make it faster. */
+               if (bpf_object__shndx_is_maps(obj, shdr_idx) ||
+                   bpf_object__shndx_is_data(obj, shdr_idx)) {
+                       type = bpf_object__section_to_libbpf_map_type(obj, shdr_idx);
+                       if (type != LIBBPF_MAP_UNSPEC &&
+                           GELF_ST_BIND(sym.st_info) == STB_GLOBAL) {
+                               pr_warning("bpf: relocation: not yet supported relo for non-static global \'%s\' variable found in insns[%d].code 0x%x\n",
+                                          name, insn_idx, insns[insn_idx].code);
+                               return -LIBBPF_ERRNO__RELOC;
+                       }
+
                        for (map_idx = 0; map_idx < nr_maps; map_idx++) {
-                               if (maps[map_idx].offset == sym.st_value) {
+                               if (maps[map_idx].libbpf_type != type)
+                                       continue;
+                               if (type != LIBBPF_MAP_UNSPEC ||
+                                   (type == LIBBPF_MAP_UNSPEC &&
+                                    maps[map_idx].offset == sym.st_value)) {
                                        pr_debug("relocation: find map %zd (%s) for insn %u\n",
                                                 map_idx, maps[map_idx].name, insn_idx);
                                        break;
                                return -LIBBPF_ERRNO__RELOC;
                        }
 
-                       prog->reloc_desc[i].type = RELO_LD64;
+                       prog->reloc_desc[i].type = type != LIBBPF_MAP_UNSPEC ?
+                                                  RELO_DATA : RELO_LD64;
                        prog->reloc_desc[i].insn_idx = insn_idx;
                        prog->reloc_desc[i].map_idx = map_idx;
                }
 static int bpf_map_find_btf_info(struct bpf_map *map, const struct btf *btf)
 {
        struct bpf_map_def *def = &map->def;
-       __u32 key_type_id, value_type_id;
+       __u32 key_type_id = 0, value_type_id = 0;
        int ret;
 
-       ret = btf__get_map_kv_tids(btf, map->name, def->key_size,
-                                  def->value_size, &key_type_id,
-                                  &value_type_id);
-       if (ret)
+       if (!bpf_map__is_internal(map)) {
+               ret = btf__get_map_kv_tids(btf, map->name, def->key_size,
+                                          def->value_size, &key_type_id,
+                                          &value_type_id);
+       } else {
+               /*
+                * LLVM annotates global data differently in BTF, that is,
+                * only as '.data', '.bss' or '.rodata'.
+                */
+               ret = btf__find_by_name(btf,
+                               libbpf_type_to_btf_name[map->libbpf_type]);
+       }
+       if (ret < 0)
                return ret;
 
        map->btf_key_type_id = key_type_id;
-       map->btf_value_type_id = value_type_id;
-
+       map->btf_value_type_id = bpf_map__is_internal(map) ?
+                                ret : value_type_id;
        return 0;
 }
 
        return bpf_object__probe_name(obj);
 }
 
+static int
+bpf_object__populate_internal_map(struct bpf_object *obj, struct bpf_map *map)
+{
+       char *cp, errmsg[STRERR_BUFSIZE];
+       int err, zero = 0;
+       __u8 *data;
+
+       /* Nothing to do here since kernel already zero-initializes .bss map. */
+       if (map->libbpf_type == LIBBPF_MAP_BSS)
+               return 0;
+
+       data = map->libbpf_type == LIBBPF_MAP_DATA ?
+              obj->sections.data : obj->sections.rodata;
+
+       err = bpf_map_update_elem(map->fd, &zero, data, 0);
+       /* Freeze .rodata map as read-only from syscall side. */
+       if (!err && map->libbpf_type == LIBBPF_MAP_RODATA) {
+               err = bpf_map_freeze(map->fd);
+               if (err) {
+                       cp = libbpf_strerror_r(errno, errmsg, sizeof(errmsg));
+                       pr_warning("Error freezing map(%s) as read-only: %s\n",
+                                  map->name, cp);
+                       err = 0;
+               }
+       }
+       return err;
+}
+
 static int
 bpf_object__create_maps(struct bpf_object *obj)
 {
                        size_t j;
 
                        err = *pfd;
+err_out:
                        cp = libbpf_strerror_r(errno, errmsg, sizeof(errmsg));
                        pr_warning("failed to create map (name: '%s'): %s\n",
                                   map->name, cp);
                                zclose(obj->maps[j].fd);
                        return err;
                }
+
+               if (bpf_map__is_internal(map)) {
+                       err = bpf_object__populate_internal_map(obj, map);
+                       if (err < 0) {
+                               zclose(*pfd);
+                               goto err_out;
+                       }
+               }
+
                pr_debug("create map %s: fd=%d\n", map->name, *pfd);
        }
 
                return 0;
 
        for (i = 0; i < prog->nr_reloc; i++) {
-               if (prog->reloc_desc[i].type == RELO_LD64) {
+               if (prog->reloc_desc[i].type == RELO_LD64 ||
+                   prog->reloc_desc[i].type == RELO_DATA) {
+                       bool relo_data = prog->reloc_desc[i].type == RELO_DATA;
                        struct bpf_insn *insns = prog->insns;
                        int insn_idx, map_idx;
 
                        insn_idx = prog->reloc_desc[i].insn_idx;
                        map_idx = prog->reloc_desc[i].map_idx;
 
-                       if (insn_idx >= (int)prog->insns_cnt) {
+                       if (insn_idx + 1 >= (int)prog->insns_cnt) {
                                pr_warning("relocation out of range: '%s'\n",
                                           prog->section_name);
                                return -LIBBPF_ERRNO__RELOC;
                        }
-                       insns[insn_idx].src_reg = BPF_PSEUDO_MAP_FD;
+
+                       if (!relo_data) {
+                               insns[insn_idx].src_reg = BPF_PSEUDO_MAP_FD;
+                       } else {
+                               insns[insn_idx].src_reg = BPF_PSEUDO_MAP_VALUE;
+                               insns[insn_idx + 1].imm = insns[insn_idx].imm;
+                       }
                        insns[insn_idx].imm = obj->maps[map_idx].fd;
                } else if (prog->reloc_desc[i].type == RELO_CALL) {
                        err = bpf_program__reloc_text(prog, obj,
                obj->maps[i].priv = NULL;
                obj->maps[i].clear_priv = NULL;
        }
+
+       zfree(&obj->sections.rodata);
+       zfree(&obj->sections.data);
        zfree(&obj->maps);
        obj->nr_maps = 0;
 
        return map->def.type == BPF_MAP_TYPE_PERF_EVENT_ARRAY;
 }
 
+bool bpf_map__is_internal(struct bpf_map *map)
+{
+       return map->libbpf_type != LIBBPF_MAP_UNSPEC;
+}
+
 void bpf_map__set_ifindex(struct bpf_map *map, __u32 ifindex)
 {
        map->map_ifindex = ifindex;