mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-14 15:54:15 +08:00
Merge branch 'btf2c-converter'
Andrii Nakryiko says: ==================== This patch set adds BTF-to-C dumping APIs to libbpf, allowing to output a subset of BTF types as a compilable C type definitions. This is useful by itself, as raw BTF output is not easy to inspect and comprehend. But it's also a big part of BPF CO-RE (compile once - run everywhere) initiative aimed at allowing to write relocatable BPF programs, that won't require on-the-host kernel headers (and would be able to inspect internal kernel structures, not exposed through kernel headers). This patch set consists of three groups of patches and one pre-patch, with the BTF-to-C dumper API depending on the first two groups. Pre-patch #1 fixes issue with libbpf_internal.h. btf__parse_elf() API patches: - patch #2 adds btf__parse_elf() API to libbpf, allowing to load BTF and/or BTF.ext from ELF file; - patch #3 utilizies btf__parse_elf() from bpftool for `btf dump file` command; - patch #4 switches test_btf.c to use btf__parse_elf() to check for presence of BTF data in object file. libbpf's internal hashmap patches: - patch #5 adds resizeable non-thread safe generic hashmap to libbpf; - patch #6 adds tests for that hashmap; - patch #7 migrates btf_dedup()'s dedup_table to use hashmap w/ APPEND. BTF-to-C dumper API patches: - patch #8 adds btf_dump APIs with all the logic for laying out type definitions in correct order and emitting C syntax for them; - patch #9 adds lots of tests for common and quirky parts of C type system; - patch #10 adds support for C-syntax btf dumping to bpftool; - patch #11 updates bpftool documentation to mention C-syntax dump option; - patch #12 update bash-completion for btf dump sub-command. v2->v3: - fix bpftool-btf.rst formatting (Quentin); - simplify bash autocompletion script (Quentin); - better error message in btf dump (Quentin); v1->v2: - removed unuseful file header (Jakub); - removed inlines in .c (Jakub); - added 'format {c|raw}' keyword/option (Jakub); - re-use i var for iteration in btf_dump_c() (Jakub); - bumped libbpf version to 0.0.4; v0->v1: - fix bug in hashmap__for_each_bucket_entry() not handling empty hashmap; - removed `btf dump`-specific libbpf logging hook up (Quentin has more generic patchset); - change btf__parse_elf() to always load .BTF and return it as a result, with .BTF.ext being optional and returned through struct btf_ext** arg (Alexei); - endianness check to use __BYTE_ORDER__ (Alexei); - bool:1 to __u8:1 in type_aux_state (Alexei); - added HASHMAP_APPEND strategy to hashmap, changed hashmap__for_each_key_entry() to also check for key equality during iteration (multimap iteration for key); - added new tests for empty hashmap and hashmap as a multimap; - tried to clarify weak/strong dependency ordering comments (Alexei) - btf dump test's expected output - support better commenting aproach (Alexei); - added bash-completion for a new "c" option (Alexei). ==================== Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
commit
5420f32077
@ -19,10 +19,11 @@ SYNOPSIS
|
||||
BTF COMMANDS
|
||||
=============
|
||||
|
||||
| **bpftool** **btf dump** *BTF_SRC*
|
||||
| **bpftool** **btf dump** *BTF_SRC* [**format** *FORMAT*]
|
||||
| **bpftool** **btf help**
|
||||
|
|
||||
| *BTF_SRC* := { **id** *BTF_ID* | **prog** *PROG* | **map** *MAP* [{**key** | **value** | **kv** | **all**}] | **file** *FILE* }
|
||||
| *FORMAT* := { **raw** | **c** }
|
||||
| *MAP* := { **id** *MAP_ID* | **pinned** *FILE* }
|
||||
| *PROG* := { **id** *PROG_ID* | **pinned** *FILE* | **tag** *PROG_TAG* }
|
||||
|
||||
@ -31,23 +32,27 @@ DESCRIPTION
|
||||
**bpftool btf dump** *BTF_SRC*
|
||||
Dump BTF entries from a given *BTF_SRC*.
|
||||
|
||||
When **id** is specified, BTF object with that ID will be
|
||||
loaded and all its BTF types emitted.
|
||||
When **id** is specified, BTF object with that ID will be
|
||||
loaded and all its BTF types emitted.
|
||||
|
||||
When **map** is provided, it's expected that map has
|
||||
associated BTF object with BTF types describing key and
|
||||
value. It's possible to select whether to dump only BTF
|
||||
type(s) associated with key (**key**), value (**value**),
|
||||
both key and value (**kv**), or all BTF types present in
|
||||
associated BTF object (**all**). If not specified, **kv**
|
||||
is assumed.
|
||||
When **map** is provided, it's expected that map has
|
||||
associated BTF object with BTF types describing key and
|
||||
value. It's possible to select whether to dump only BTF
|
||||
type(s) associated with key (**key**), value (**value**),
|
||||
both key and value (**kv**), or all BTF types present in
|
||||
associated BTF object (**all**). If not specified, **kv**
|
||||
is assumed.
|
||||
|
||||
When **prog** is provided, it's expected that program has
|
||||
associated BTF object with BTF types.
|
||||
When **prog** is provided, it's expected that program has
|
||||
associated BTF object with BTF types.
|
||||
|
||||
When specifying *FILE*, an ELF file is expected, containing
|
||||
.BTF section with well-defined BTF binary format data,
|
||||
typically produced by clang or pahole.
|
||||
When specifying *FILE*, an ELF file is expected, containing
|
||||
.BTF section with well-defined BTF binary format data,
|
||||
typically produced by clang or pahole.
|
||||
|
||||
**format** option can be used to override default (raw)
|
||||
output format. Raw (**raw**) or C-syntax (**c**) output
|
||||
formats are supported.
|
||||
|
||||
**bpftool btf help**
|
||||
Print short help message.
|
||||
|
@ -638,11 +638,24 @@ _bpftool()
|
||||
esac
|
||||
return 0
|
||||
;;
|
||||
format)
|
||||
COMPREPLY=( $( compgen -W "c raw" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
if [[ $cword == 6 ]] && [[ ${words[3]} == "map" ]]; then
|
||||
COMPREPLY+=( $( compgen -W 'key value kv all' -- \
|
||||
"$cur" ) )
|
||||
fi
|
||||
# emit extra options
|
||||
case ${words[3]} in
|
||||
id|file)
|
||||
_bpftool_once_attr 'format'
|
||||
;;
|
||||
map|prog)
|
||||
if [[ ${words[3]} == "map" ]] && [[ $cword == 6 ]]; then
|
||||
COMPREPLY+=( $( compgen -W "key value kv all" -- "$cur" ) )
|
||||
fi
|
||||
_bpftool_once_attr 'format'
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
@ -8,8 +8,8 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <gelf.h>
|
||||
#include <bpf.h>
|
||||
#include <libbpf.h>
|
||||
#include <linux/btf.h>
|
||||
|
||||
#include "btf.h"
|
||||
@ -340,109 +340,40 @@ static int dump_btf_raw(const struct btf *btf,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool check_btf_endianness(GElf_Ehdr *ehdr)
|
||||
static void __printf(2, 0) btf_dump_printf(void *ctx,
|
||||
const char *fmt, va_list args)
|
||||
{
|
||||
static unsigned int const endian = 1;
|
||||
|
||||
switch (ehdr->e_ident[EI_DATA]) {
|
||||
case ELFDATA2LSB:
|
||||
return *(unsigned char const *)&endian == 1;
|
||||
case ELFDATA2MSB:
|
||||
return *(unsigned char const *)&endian == 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
vfprintf(stdout, fmt, args);
|
||||
}
|
||||
|
||||
static int btf_load_from_elf(const char *path, struct btf **btf)
|
||||
static int dump_btf_c(const struct btf *btf,
|
||||
__u32 *root_type_ids, int root_type_cnt)
|
||||
{
|
||||
int err = -1, fd = -1, idx = 0;
|
||||
Elf_Data *btf_data = NULL;
|
||||
Elf_Scn *scn = NULL;
|
||||
Elf *elf = NULL;
|
||||
GElf_Ehdr ehdr;
|
||||
struct btf_dump *d;
|
||||
int err = 0, i;
|
||||
|
||||
if (elf_version(EV_CURRENT) == EV_NONE) {
|
||||
p_err("failed to init libelf for %s", path);
|
||||
return -1;
|
||||
}
|
||||
d = btf_dump__new(btf, NULL, NULL, btf_dump_printf);
|
||||
if (IS_ERR(d))
|
||||
return PTR_ERR(d);
|
||||
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
p_err("failed to open %s: %s", path, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
elf = elf_begin(fd, ELF_C_READ, NULL);
|
||||
if (!elf) {
|
||||
p_err("failed to open %s as ELF file", path);
|
||||
goto done;
|
||||
}
|
||||
if (!gelf_getehdr(elf, &ehdr)) {
|
||||
p_err("failed to get EHDR from %s", path);
|
||||
goto done;
|
||||
}
|
||||
if (!check_btf_endianness(&ehdr)) {
|
||||
p_err("non-native ELF endianness is not supported");
|
||||
goto done;
|
||||
}
|
||||
if (!elf_rawdata(elf_getscn(elf, ehdr.e_shstrndx), NULL)) {
|
||||
p_err("failed to get e_shstrndx from %s\n", path);
|
||||
goto done;
|
||||
}
|
||||
|
||||
while ((scn = elf_nextscn(elf, scn)) != NULL) {
|
||||
GElf_Shdr sh;
|
||||
char *name;
|
||||
|
||||
idx++;
|
||||
if (gelf_getshdr(scn, &sh) != &sh) {
|
||||
p_err("failed to get section(%d) header from %s",
|
||||
idx, path);
|
||||
goto done;
|
||||
}
|
||||
name = elf_strptr(elf, ehdr.e_shstrndx, sh.sh_name);
|
||||
if (!name) {
|
||||
p_err("failed to get section(%d) name from %s",
|
||||
idx, path);
|
||||
goto done;
|
||||
}
|
||||
if (strcmp(name, BTF_ELF_SEC) == 0) {
|
||||
btf_data = elf_getdata(scn, 0);
|
||||
if (!btf_data) {
|
||||
p_err("failed to get section(%d, %s) data from %s",
|
||||
idx, name, path);
|
||||
if (root_type_cnt) {
|
||||
for (i = 0; i < root_type_cnt; i++) {
|
||||
err = btf_dump__dump_type(d, root_type_ids[i]);
|
||||
if (err)
|
||||
goto done;
|
||||
}
|
||||
} else {
|
||||
int cnt = btf__get_nr_types(btf);
|
||||
|
||||
for (i = 1; i <= cnt; i++) {
|
||||
err = btf_dump__dump_type(d, i);
|
||||
if (err)
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!btf_data) {
|
||||
p_err("%s ELF section not found in %s", BTF_ELF_SEC, path);
|
||||
goto done;
|
||||
}
|
||||
|
||||
*btf = btf__new(btf_data->d_buf, btf_data->d_size);
|
||||
if (IS_ERR(*btf)) {
|
||||
err = PTR_ERR(*btf);
|
||||
*btf = NULL;
|
||||
p_err("failed to load BTF data from %s: %s",
|
||||
path, strerror(err));
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = 0;
|
||||
done:
|
||||
if (err) {
|
||||
if (*btf) {
|
||||
btf__free(*btf);
|
||||
*btf = NULL;
|
||||
}
|
||||
}
|
||||
if (elf)
|
||||
elf_end(elf);
|
||||
close(fd);
|
||||
btf_dump__free(d);
|
||||
return err;
|
||||
}
|
||||
|
||||
@ -451,6 +382,7 @@ static int do_dump(int argc, char **argv)
|
||||
struct btf *btf = NULL;
|
||||
__u32 root_type_ids[2];
|
||||
int root_type_cnt = 0;
|
||||
bool dump_c = false;
|
||||
__u32 btf_id = -1;
|
||||
const char *src;
|
||||
int fd = -1;
|
||||
@ -522,9 +454,14 @@ static int do_dump(int argc, char **argv)
|
||||
}
|
||||
NEXT_ARG();
|
||||
} else if (is_prefix(src, "file")) {
|
||||
err = btf_load_from_elf(*argv, &btf);
|
||||
if (err)
|
||||
btf = btf__parse_elf(*argv, NULL);
|
||||
if (IS_ERR(btf)) {
|
||||
err = PTR_ERR(btf);
|
||||
btf = NULL;
|
||||
p_err("failed to load BTF from %s: %s",
|
||||
*argv, strerror(err));
|
||||
goto done;
|
||||
}
|
||||
NEXT_ARG();
|
||||
} else {
|
||||
err = -1;
|
||||
@ -532,6 +469,29 @@ static int do_dump(int argc, char **argv)
|
||||
goto done;
|
||||
}
|
||||
|
||||
while (argc) {
|
||||
if (is_prefix(*argv, "format")) {
|
||||
NEXT_ARG();
|
||||
if (argc < 1) {
|
||||
p_err("expecting value for 'format' option\n");
|
||||
goto done;
|
||||
}
|
||||
if (strcmp(*argv, "c") == 0) {
|
||||
dump_c = true;
|
||||
} else if (strcmp(*argv, "raw") == 0) {
|
||||
dump_c = false;
|
||||
} else {
|
||||
p_err("unrecognized format specifier: '%s', possible values: raw, c",
|
||||
*argv);
|
||||
goto done;
|
||||
}
|
||||
NEXT_ARG();
|
||||
} else {
|
||||
p_err("unrecognized option: '%s'", *argv);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (!btf) {
|
||||
err = btf__get_from_id(btf_id, &btf);
|
||||
if (err) {
|
||||
@ -545,7 +505,16 @@ static int do_dump(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
dump_btf_raw(btf, root_type_ids, root_type_cnt);
|
||||
if (dump_c) {
|
||||
if (json_output) {
|
||||
p_err("JSON output for C-syntax dump is not supported");
|
||||
err = -ENOTSUP;
|
||||
goto done;
|
||||
}
|
||||
err = dump_btf_c(btf, root_type_ids, root_type_cnt);
|
||||
} else {
|
||||
err = dump_btf_raw(btf, root_type_ids, root_type_cnt);
|
||||
}
|
||||
|
||||
done:
|
||||
close(fd);
|
||||
@ -561,10 +530,11 @@ static int do_help(int argc, char **argv)
|
||||
}
|
||||
|
||||
fprintf(stderr,
|
||||
"Usage: %s btf dump BTF_SRC\n"
|
||||
"Usage: %s btf dump BTF_SRC [format FORMAT]\n"
|
||||
" %s btf help\n"
|
||||
"\n"
|
||||
" BTF_SRC := { id BTF_ID | prog PROG | map MAP [{key | value | kv | all}] | file FILE }\n"
|
||||
" FORMAT := { raw | c }\n"
|
||||
" " HELP_SPEC_MAP "\n"
|
||||
" " HELP_SPEC_PROGRAM "\n"
|
||||
" " HELP_SPEC_OPTIONS "\n"
|
||||
|
@ -1 +1,3 @@
|
||||
libbpf-y := libbpf.o bpf.o nlattr.o btf.o libbpf_errno.o str_error.o netlink.o bpf_prog_linfo.o libbpf_probes.o xsk.o
|
||||
libbpf-y := libbpf.o bpf.o nlattr.o btf.o libbpf_errno.o str_error.o \
|
||||
netlink.o bpf_prog_linfo.o libbpf_probes.o xsk.o hashmap.o \
|
||||
btf_dump.o
|
||||
|
@ -4,14 +4,17 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/btf.h>
|
||||
#include <gelf.h>
|
||||
#include "btf.h"
|
||||
#include "bpf.h"
|
||||
#include "libbpf.h"
|
||||
#include "libbpf_internal.h"
|
||||
#include "hashmap.h"
|
||||
|
||||
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
@ -417,6 +420,132 @@ done:
|
||||
return btf;
|
||||
}
|
||||
|
||||
static bool btf_check_endianness(const GElf_Ehdr *ehdr)
|
||||
{
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
return ehdr->e_ident[EI_DATA] == ELFDATA2LSB;
|
||||
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
return ehdr->e_ident[EI_DATA] == ELFDATA2MSB;
|
||||
#else
|
||||
# error "Unrecognized __BYTE_ORDER__"
|
||||
#endif
|
||||
}
|
||||
|
||||
struct btf *btf__parse_elf(const char *path, struct btf_ext **btf_ext)
|
||||
{
|
||||
Elf_Data *btf_data = NULL, *btf_ext_data = NULL;
|
||||
int err = 0, fd = -1, idx = 0;
|
||||
struct btf *btf = NULL;
|
||||
Elf_Scn *scn = NULL;
|
||||
Elf *elf = NULL;
|
||||
GElf_Ehdr ehdr;
|
||||
|
||||
if (elf_version(EV_CURRENT) == EV_NONE) {
|
||||
pr_warning("failed to init libelf for %s\n", path);
|
||||
return ERR_PTR(-LIBBPF_ERRNO__LIBELF);
|
||||
}
|
||||
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
err = -errno;
|
||||
pr_warning("failed to open %s: %s\n", path, strerror(errno));
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
err = -LIBBPF_ERRNO__FORMAT;
|
||||
|
||||
elf = elf_begin(fd, ELF_C_READ, NULL);
|
||||
if (!elf) {
|
||||
pr_warning("failed to open %s as ELF file\n", path);
|
||||
goto done;
|
||||
}
|
||||
if (!gelf_getehdr(elf, &ehdr)) {
|
||||
pr_warning("failed to get EHDR from %s\n", path);
|
||||
goto done;
|
||||
}
|
||||
if (!btf_check_endianness(&ehdr)) {
|
||||
pr_warning("non-native ELF endianness is not supported\n");
|
||||
goto done;
|
||||
}
|
||||
if (!elf_rawdata(elf_getscn(elf, ehdr.e_shstrndx), NULL)) {
|
||||
pr_warning("failed to get e_shstrndx from %s\n", path);
|
||||
goto done;
|
||||
}
|
||||
|
||||
while ((scn = elf_nextscn(elf, scn)) != NULL) {
|
||||
GElf_Shdr sh;
|
||||
char *name;
|
||||
|
||||
idx++;
|
||||
if (gelf_getshdr(scn, &sh) != &sh) {
|
||||
pr_warning("failed to get section(%d) header from %s\n",
|
||||
idx, path);
|
||||
goto done;
|
||||
}
|
||||
name = elf_strptr(elf, ehdr.e_shstrndx, sh.sh_name);
|
||||
if (!name) {
|
||||
pr_warning("failed to get section(%d) name from %s\n",
|
||||
idx, path);
|
||||
goto done;
|
||||
}
|
||||
if (strcmp(name, BTF_ELF_SEC) == 0) {
|
||||
btf_data = elf_getdata(scn, 0);
|
||||
if (!btf_data) {
|
||||
pr_warning("failed to get section(%d, %s) data from %s\n",
|
||||
idx, name, path);
|
||||
goto done;
|
||||
}
|
||||
continue;
|
||||
} else if (btf_ext && strcmp(name, BTF_EXT_ELF_SEC) == 0) {
|
||||
btf_ext_data = elf_getdata(scn, 0);
|
||||
if (!btf_ext_data) {
|
||||
pr_warning("failed to get section(%d, %s) data from %s\n",
|
||||
idx, name, path);
|
||||
goto done;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
err = 0;
|
||||
|
||||
if (!btf_data) {
|
||||
err = -ENOENT;
|
||||
goto done;
|
||||
}
|
||||
btf = btf__new(btf_data->d_buf, btf_data->d_size);
|
||||
if (IS_ERR(btf))
|
||||
goto done;
|
||||
|
||||
if (btf_ext && btf_ext_data) {
|
||||
*btf_ext = btf_ext__new(btf_ext_data->d_buf,
|
||||
btf_ext_data->d_size);
|
||||
if (IS_ERR(*btf_ext))
|
||||
goto done;
|
||||
} else if (btf_ext) {
|
||||
*btf_ext = NULL;
|
||||
}
|
||||
done:
|
||||
if (elf)
|
||||
elf_end(elf);
|
||||
close(fd);
|
||||
|
||||
if (err)
|
||||
return ERR_PTR(err);
|
||||
/*
|
||||
* btf is always parsed before btf_ext, so no need to clean up
|
||||
* btf_ext, if btf loading failed
|
||||
*/
|
||||
if (IS_ERR(btf))
|
||||
return btf;
|
||||
if (btf_ext && IS_ERR(*btf_ext)) {
|
||||
btf__free(btf);
|
||||
err = PTR_ERR(*btf_ext);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
return btf;
|
||||
}
|
||||
|
||||
static int compare_vsi_off(const void *_a, const void *_b)
|
||||
{
|
||||
const struct btf_var_secinfo *a = _a;
|
||||
@ -1165,16 +1294,9 @@ done:
|
||||
return err;
|
||||
}
|
||||
|
||||
#define BTF_DEDUP_TABLE_DEFAULT_SIZE (1 << 14)
|
||||
#define BTF_DEDUP_TABLE_MAX_SIZE_LOG 31
|
||||
#define BTF_UNPROCESSED_ID ((__u32)-1)
|
||||
#define BTF_IN_PROGRESS_ID ((__u32)-2)
|
||||
|
||||
struct btf_dedup_node {
|
||||
struct btf_dedup_node *next;
|
||||
__u32 type_id;
|
||||
};
|
||||
|
||||
struct btf_dedup {
|
||||
/* .BTF section to be deduped in-place */
|
||||
struct btf *btf;
|
||||
@ -1190,7 +1312,7 @@ struct btf_dedup {
|
||||
* candidates, which is fine because we rely on subsequent
|
||||
* btf_xxx_equal() checks to authoritatively verify type equality.
|
||||
*/
|
||||
struct btf_dedup_node **dedup_table;
|
||||
struct hashmap *dedup_table;
|
||||
/* Canonical types map */
|
||||
__u32 *map;
|
||||
/* Hypothetical mapping, used during type graph equivalence checks */
|
||||
@ -1215,30 +1337,18 @@ struct btf_str_ptrs {
|
||||
__u32 cap;
|
||||
};
|
||||
|
||||
static inline __u32 hash_combine(__u32 h, __u32 value)
|
||||
static long hash_combine(long h, long value)
|
||||
{
|
||||
/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */
|
||||
#define GOLDEN_RATIO_PRIME 0x9e370001UL
|
||||
return h * 37 + value * GOLDEN_RATIO_PRIME;
|
||||
#undef GOLDEN_RATIO_PRIME
|
||||
return h * 31 + value;
|
||||
}
|
||||
|
||||
#define for_each_dedup_cand(d, hash, node) \
|
||||
for (node = d->dedup_table[hash & (d->opts.dedup_table_size - 1)]; \
|
||||
node; \
|
||||
node = node->next)
|
||||
#define for_each_dedup_cand(d, node, hash) \
|
||||
hashmap__for_each_key_entry(d->dedup_table, node, (void *)hash)
|
||||
|
||||
static int btf_dedup_table_add(struct btf_dedup *d, __u32 hash, __u32 type_id)
|
||||
static int btf_dedup_table_add(struct btf_dedup *d, long hash, __u32 type_id)
|
||||
{
|
||||
struct btf_dedup_node *node = malloc(sizeof(struct btf_dedup_node));
|
||||
int bucket = hash & (d->opts.dedup_table_size - 1);
|
||||
|
||||
if (!node)
|
||||
return -ENOMEM;
|
||||
node->type_id = type_id;
|
||||
node->next = d->dedup_table[bucket];
|
||||
d->dedup_table[bucket] = node;
|
||||
return 0;
|
||||
return hashmap__append(d->dedup_table,
|
||||
(void *)hash, (void *)(long)type_id);
|
||||
}
|
||||
|
||||
static int btf_dedup_hypot_map_add(struct btf_dedup *d,
|
||||
@ -1267,36 +1377,10 @@ static void btf_dedup_clear_hypot_map(struct btf_dedup *d)
|
||||
d->hypot_cnt = 0;
|
||||
}
|
||||
|
||||
static void btf_dedup_table_free(struct btf_dedup *d)
|
||||
{
|
||||
struct btf_dedup_node *head, *tmp;
|
||||
int i;
|
||||
|
||||
if (!d->dedup_table)
|
||||
return;
|
||||
|
||||
for (i = 0; i < d->opts.dedup_table_size; i++) {
|
||||
while (d->dedup_table[i]) {
|
||||
tmp = d->dedup_table[i];
|
||||
d->dedup_table[i] = tmp->next;
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
head = d->dedup_table[i];
|
||||
while (head) {
|
||||
tmp = head;
|
||||
head = head->next;
|
||||
free(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
free(d->dedup_table);
|
||||
d->dedup_table = NULL;
|
||||
}
|
||||
|
||||
static void btf_dedup_free(struct btf_dedup *d)
|
||||
{
|
||||
btf_dedup_table_free(d);
|
||||
hashmap__free(d->dedup_table);
|
||||
d->dedup_table = NULL;
|
||||
|
||||
free(d->map);
|
||||
d->map = NULL;
|
||||
@ -1310,40 +1394,43 @@ static void btf_dedup_free(struct btf_dedup *d)
|
||||
free(d);
|
||||
}
|
||||
|
||||
/* Find closest power of two >= to size, capped at 2^max_size_log */
|
||||
static __u32 roundup_pow2_max(__u32 size, int max_size_log)
|
||||
static size_t btf_dedup_identity_hash_fn(const void *key, void *ctx)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < max_size_log && (1U << i) < size; i++)
|
||||
;
|
||||
return 1U << i;
|
||||
return (size_t)key;
|
||||
}
|
||||
|
||||
static size_t btf_dedup_collision_hash_fn(const void *key, void *ctx)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool btf_dedup_equal_fn(const void *k1, const void *k2, void *ctx)
|
||||
{
|
||||
return k1 == k2;
|
||||
}
|
||||
|
||||
static struct btf_dedup *btf_dedup_new(struct btf *btf, struct btf_ext *btf_ext,
|
||||
const struct btf_dedup_opts *opts)
|
||||
{
|
||||
struct btf_dedup *d = calloc(1, sizeof(struct btf_dedup));
|
||||
hashmap_hash_fn hash_fn = btf_dedup_identity_hash_fn;
|
||||
int i, err = 0;
|
||||
__u32 sz;
|
||||
|
||||
if (!d)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
d->opts.dont_resolve_fwds = opts && opts->dont_resolve_fwds;
|
||||
sz = opts && opts->dedup_table_size ? opts->dedup_table_size
|
||||
: BTF_DEDUP_TABLE_DEFAULT_SIZE;
|
||||
sz = roundup_pow2_max(sz, BTF_DEDUP_TABLE_MAX_SIZE_LOG);
|
||||
d->opts.dedup_table_size = sz;
|
||||
/* dedup_table_size is now used only to force collisions in tests */
|
||||
if (opts && opts->dedup_table_size == 1)
|
||||
hash_fn = btf_dedup_collision_hash_fn;
|
||||
|
||||
d->btf = btf;
|
||||
d->btf_ext = btf_ext;
|
||||
|
||||
d->dedup_table = calloc(d->opts.dedup_table_size,
|
||||
sizeof(struct btf_dedup_node *));
|
||||
if (!d->dedup_table) {
|
||||
err = -ENOMEM;
|
||||
d->dedup_table = hashmap__new(hash_fn, btf_dedup_equal_fn, NULL);
|
||||
if (IS_ERR(d->dedup_table)) {
|
||||
err = PTR_ERR(d->dedup_table);
|
||||
d->dedup_table = NULL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
@ -1662,9 +1749,9 @@ done:
|
||||
return err;
|
||||
}
|
||||
|
||||
static __u32 btf_hash_common(struct btf_type *t)
|
||||
static long btf_hash_common(struct btf_type *t)
|
||||
{
|
||||
__u32 h;
|
||||
long h;
|
||||
|
||||
h = hash_combine(0, t->name_off);
|
||||
h = hash_combine(h, t->info);
|
||||
@ -1680,10 +1767,10 @@ static bool btf_equal_common(struct btf_type *t1, struct btf_type *t2)
|
||||
}
|
||||
|
||||
/* Calculate type signature hash of INT. */
|
||||
static __u32 btf_hash_int(struct btf_type *t)
|
||||
static long btf_hash_int(struct btf_type *t)
|
||||
{
|
||||
__u32 info = *(__u32 *)(t + 1);
|
||||
__u32 h;
|
||||
long h;
|
||||
|
||||
h = btf_hash_common(t);
|
||||
h = hash_combine(h, info);
|
||||
@ -1703,9 +1790,9 @@ static bool btf_equal_int(struct btf_type *t1, struct btf_type *t2)
|
||||
}
|
||||
|
||||
/* Calculate type signature hash of ENUM. */
|
||||
static __u32 btf_hash_enum(struct btf_type *t)
|
||||
static long btf_hash_enum(struct btf_type *t)
|
||||
{
|
||||
__u32 h;
|
||||
long h;
|
||||
|
||||
/* don't hash vlen and enum members to support enum fwd resolving */
|
||||
h = hash_combine(0, t->name_off);
|
||||
@ -1757,11 +1844,11 @@ static bool btf_compat_enum(struct btf_type *t1, struct btf_type *t2)
|
||||
* as referenced type IDs equivalence is established separately during type
|
||||
* graph equivalence check algorithm.
|
||||
*/
|
||||
static __u32 btf_hash_struct(struct btf_type *t)
|
||||
static long btf_hash_struct(struct btf_type *t)
|
||||
{
|
||||
struct btf_member *member = (struct btf_member *)(t + 1);
|
||||
__u32 vlen = BTF_INFO_VLEN(t->info);
|
||||
__u32 h = btf_hash_common(t);
|
||||
long h = btf_hash_common(t);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < vlen; i++) {
|
||||
@ -1804,10 +1891,10 @@ static bool btf_shallow_equal_struct(struct btf_type *t1, struct btf_type *t2)
|
||||
* under assumption that they were already resolved to canonical type IDs and
|
||||
* are not going to change.
|
||||
*/
|
||||
static __u32 btf_hash_array(struct btf_type *t)
|
||||
static long btf_hash_array(struct btf_type *t)
|
||||
{
|
||||
struct btf_array *info = (struct btf_array *)(t + 1);
|
||||
__u32 h = btf_hash_common(t);
|
||||
long h = btf_hash_common(t);
|
||||
|
||||
h = hash_combine(h, info->type);
|
||||
h = hash_combine(h, info->index_type);
|
||||
@ -1858,11 +1945,11 @@ static bool btf_compat_array(struct btf_type *t1, struct btf_type *t2)
|
||||
* under assumption that they were already resolved to canonical type IDs and
|
||||
* are not going to change.
|
||||
*/
|
||||
static inline __u32 btf_hash_fnproto(struct btf_type *t)
|
||||
static long btf_hash_fnproto(struct btf_type *t)
|
||||
{
|
||||
struct btf_param *member = (struct btf_param *)(t + 1);
|
||||
__u16 vlen = BTF_INFO_VLEN(t->info);
|
||||
__u32 h = btf_hash_common(t);
|
||||
long h = btf_hash_common(t);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < vlen; i++) {
|
||||
@ -1880,7 +1967,7 @@ static inline __u32 btf_hash_fnproto(struct btf_type *t)
|
||||
* This function is called during reference types deduplication to compare
|
||||
* FUNC_PROTO to potential canonical representative.
|
||||
*/
|
||||
static inline bool btf_equal_fnproto(struct btf_type *t1, struct btf_type *t2)
|
||||
static bool btf_equal_fnproto(struct btf_type *t1, struct btf_type *t2)
|
||||
{
|
||||
struct btf_param *m1, *m2;
|
||||
__u16 vlen;
|
||||
@ -1906,7 +1993,7 @@ static inline bool btf_equal_fnproto(struct btf_type *t1, struct btf_type *t2)
|
||||
* IDs. This check is performed during type graph equivalence check and
|
||||
* referenced types equivalence is checked separately.
|
||||
*/
|
||||
static inline bool btf_compat_fnproto(struct btf_type *t1, struct btf_type *t2)
|
||||
static bool btf_compat_fnproto(struct btf_type *t1, struct btf_type *t2)
|
||||
{
|
||||
struct btf_param *m1, *m2;
|
||||
__u16 vlen;
|
||||
@ -1937,11 +2024,12 @@ static inline bool btf_compat_fnproto(struct btf_type *t1, struct btf_type *t2)
|
||||
static int btf_dedup_prim_type(struct btf_dedup *d, __u32 type_id)
|
||||
{
|
||||
struct btf_type *t = d->btf->types[type_id];
|
||||
struct hashmap_entry *hash_entry;
|
||||
struct btf_type *cand;
|
||||
struct btf_dedup_node *cand_node;
|
||||
/* if we don't find equivalent type, then we are canonical */
|
||||
__u32 new_id = type_id;
|
||||
__u32 h;
|
||||
__u32 cand_id;
|
||||
long h;
|
||||
|
||||
switch (BTF_INFO_KIND(t->info)) {
|
||||
case BTF_KIND_CONST:
|
||||
@ -1960,10 +2048,11 @@ static int btf_dedup_prim_type(struct btf_dedup *d, __u32 type_id)
|
||||
|
||||
case BTF_KIND_INT:
|
||||
h = btf_hash_int(t);
|
||||
for_each_dedup_cand(d, h, cand_node) {
|
||||
cand = d->btf->types[cand_node->type_id];
|
||||
for_each_dedup_cand(d, hash_entry, h) {
|
||||
cand_id = (__u32)(long)hash_entry->value;
|
||||
cand = d->btf->types[cand_id];
|
||||
if (btf_equal_int(t, cand)) {
|
||||
new_id = cand_node->type_id;
|
||||
new_id = cand_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1971,10 +2060,11 @@ static int btf_dedup_prim_type(struct btf_dedup *d, __u32 type_id)
|
||||
|
||||
case BTF_KIND_ENUM:
|
||||
h = btf_hash_enum(t);
|
||||
for_each_dedup_cand(d, h, cand_node) {
|
||||
cand = d->btf->types[cand_node->type_id];
|
||||
for_each_dedup_cand(d, hash_entry, h) {
|
||||
cand_id = (__u32)(long)hash_entry->value;
|
||||
cand = d->btf->types[cand_id];
|
||||
if (btf_equal_enum(t, cand)) {
|
||||
new_id = cand_node->type_id;
|
||||
new_id = cand_id;
|
||||
break;
|
||||
}
|
||||
if (d->opts.dont_resolve_fwds)
|
||||
@ -1982,21 +2072,22 @@ static int btf_dedup_prim_type(struct btf_dedup *d, __u32 type_id)
|
||||
if (btf_compat_enum(t, cand)) {
|
||||
if (btf_is_enum_fwd(t)) {
|
||||
/* resolve fwd to full enum */
|
||||
new_id = cand_node->type_id;
|
||||
new_id = cand_id;
|
||||
break;
|
||||
}
|
||||
/* resolve canonical enum fwd to full enum */
|
||||
d->map[cand_node->type_id] = type_id;
|
||||
d->map[cand_id] = type_id;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case BTF_KIND_FWD:
|
||||
h = btf_hash_common(t);
|
||||
for_each_dedup_cand(d, h, cand_node) {
|
||||
cand = d->btf->types[cand_node->type_id];
|
||||
for_each_dedup_cand(d, hash_entry, h) {
|
||||
cand_id = (__u32)(long)hash_entry->value;
|
||||
cand = d->btf->types[cand_id];
|
||||
if (btf_equal_common(t, cand)) {
|
||||
new_id = cand_node->type_id;
|
||||
new_id = cand_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -2397,12 +2488,12 @@ static void btf_dedup_merge_hypot_map(struct btf_dedup *d)
|
||||
*/
|
||||
static int btf_dedup_struct_type(struct btf_dedup *d, __u32 type_id)
|
||||
{
|
||||
struct btf_dedup_node *cand_node;
|
||||
struct btf_type *cand_type, *t;
|
||||
struct hashmap_entry *hash_entry;
|
||||
/* if we don't find equivalent type, then we are canonical */
|
||||
__u32 new_id = type_id;
|
||||
__u16 kind;
|
||||
__u32 h;
|
||||
long h;
|
||||
|
||||
/* already deduped or is in process of deduping (loop detected) */
|
||||
if (d->map[type_id] <= BTF_MAX_NR_TYPES)
|
||||
@ -2415,7 +2506,8 @@ static int btf_dedup_struct_type(struct btf_dedup *d, __u32 type_id)
|
||||
return 0;
|
||||
|
||||
h = btf_hash_struct(t);
|
||||
for_each_dedup_cand(d, h, cand_node) {
|
||||
for_each_dedup_cand(d, hash_entry, h) {
|
||||
__u32 cand_id = (__u32)(long)hash_entry->value;
|
||||
int eq;
|
||||
|
||||
/*
|
||||
@ -2428,17 +2520,17 @@ static int btf_dedup_struct_type(struct btf_dedup *d, __u32 type_id)
|
||||
* creating a loop (FWD -> STRUCT and STRUCT -> FWD), because
|
||||
* FWD and compatible STRUCT/UNION are considered equivalent.
|
||||
*/
|
||||
cand_type = d->btf->types[cand_node->type_id];
|
||||
cand_type = d->btf->types[cand_id];
|
||||
if (!btf_shallow_equal_struct(t, cand_type))
|
||||
continue;
|
||||
|
||||
btf_dedup_clear_hypot_map(d);
|
||||
eq = btf_dedup_is_equiv(d, type_id, cand_node->type_id);
|
||||
eq = btf_dedup_is_equiv(d, type_id, cand_id);
|
||||
if (eq < 0)
|
||||
return eq;
|
||||
if (!eq)
|
||||
continue;
|
||||
new_id = cand_node->type_id;
|
||||
new_id = cand_id;
|
||||
btf_dedup_merge_hypot_map(d);
|
||||
break;
|
||||
}
|
||||
@ -2488,12 +2580,12 @@ static int btf_dedup_struct_types(struct btf_dedup *d)
|
||||
*/
|
||||
static int btf_dedup_ref_type(struct btf_dedup *d, __u32 type_id)
|
||||
{
|
||||
struct btf_dedup_node *cand_node;
|
||||
struct hashmap_entry *hash_entry;
|
||||
__u32 new_id = type_id, cand_id;
|
||||
struct btf_type *t, *cand;
|
||||
/* if we don't find equivalent type, then we are representative type */
|
||||
__u32 new_id = type_id;
|
||||
int ref_type_id;
|
||||
__u32 h;
|
||||
long h;
|
||||
|
||||
if (d->map[type_id] == BTF_IN_PROGRESS_ID)
|
||||
return -ELOOP;
|
||||
@ -2516,10 +2608,11 @@ static int btf_dedup_ref_type(struct btf_dedup *d, __u32 type_id)
|
||||
t->type = ref_type_id;
|
||||
|
||||
h = btf_hash_common(t);
|
||||
for_each_dedup_cand(d, h, cand_node) {
|
||||
cand = d->btf->types[cand_node->type_id];
|
||||
for_each_dedup_cand(d, hash_entry, h) {
|
||||
cand_id = (__u32)(long)hash_entry->value;
|
||||
cand = d->btf->types[cand_id];
|
||||
if (btf_equal_common(t, cand)) {
|
||||
new_id = cand_node->type_id;
|
||||
new_id = cand_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -2539,10 +2632,11 @@ static int btf_dedup_ref_type(struct btf_dedup *d, __u32 type_id)
|
||||
info->index_type = ref_type_id;
|
||||
|
||||
h = btf_hash_array(t);
|
||||
for_each_dedup_cand(d, h, cand_node) {
|
||||
cand = d->btf->types[cand_node->type_id];
|
||||
for_each_dedup_cand(d, hash_entry, h) {
|
||||
cand_id = (__u32)(long)hash_entry->value;
|
||||
cand = d->btf->types[cand_id];
|
||||
if (btf_equal_array(t, cand)) {
|
||||
new_id = cand_node->type_id;
|
||||
new_id = cand_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -2570,10 +2664,11 @@ static int btf_dedup_ref_type(struct btf_dedup *d, __u32 type_id)
|
||||
}
|
||||
|
||||
h = btf_hash_fnproto(t);
|
||||
for_each_dedup_cand(d, h, cand_node) {
|
||||
cand = d->btf->types[cand_node->type_id];
|
||||
for_each_dedup_cand(d, hash_entry, h) {
|
||||
cand_id = (__u32)(long)hash_entry->value;
|
||||
cand = d->btf->types[cand_id];
|
||||
if (btf_equal_fnproto(t, cand)) {
|
||||
new_id = cand_node->type_id;
|
||||
new_id = cand_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -2600,7 +2695,9 @@ static int btf_dedup_ref_types(struct btf_dedup *d)
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
btf_dedup_table_free(d);
|
||||
/* we won't need d->dedup_table anymore */
|
||||
hashmap__free(d->dedup_table);
|
||||
d->dedup_table = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
#ifndef __LIBBPF_BTF_H
|
||||
#define __LIBBPF_BTF_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
@ -59,6 +60,8 @@ struct btf_ext_header {
|
||||
|
||||
LIBBPF_API void btf__free(struct btf *btf);
|
||||
LIBBPF_API struct btf *btf__new(__u8 *data, __u32 size);
|
||||
LIBBPF_API struct btf *btf__parse_elf(const char *path,
|
||||
struct btf_ext **btf_ext);
|
||||
LIBBPF_API int btf__finalize_data(struct bpf_object *obj, struct btf *btf);
|
||||
LIBBPF_API int btf__load(struct btf *btf);
|
||||
LIBBPF_API __s32 btf__find_by_name(const struct btf *btf,
|
||||
@ -100,6 +103,22 @@ struct btf_dedup_opts {
|
||||
LIBBPF_API int btf__dedup(struct btf *btf, struct btf_ext *btf_ext,
|
||||
const struct btf_dedup_opts *opts);
|
||||
|
||||
struct btf_dump;
|
||||
|
||||
struct btf_dump_opts {
|
||||
void *ctx;
|
||||
};
|
||||
|
||||
typedef void (*btf_dump_printf_fn_t)(void *ctx, const char *fmt, va_list args);
|
||||
|
||||
LIBBPF_API struct btf_dump *btf_dump__new(const struct btf *btf,
|
||||
const struct btf_ext *btf_ext,
|
||||
const struct btf_dump_opts *opts,
|
||||
btf_dump_printf_fn_t printf_fn);
|
||||
LIBBPF_API void btf_dump__free(struct btf_dump *d);
|
||||
|
||||
LIBBPF_API int btf_dump__dump_type(struct btf_dump *d, __u32 id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
1336
tools/lib/bpf/btf_dump.c
Normal file
1336
tools/lib/bpf/btf_dump.c
Normal file
File diff suppressed because it is too large
Load Diff
229
tools/lib/bpf/hashmap.c
Normal file
229
tools/lib/bpf/hashmap.c
Normal file
@ -0,0 +1,229 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
|
||||
/*
|
||||
* Generic non-thread safe hash map implementation.
|
||||
*
|
||||
* Copyright (c) 2019 Facebook
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <linux/err.h>
|
||||
#include "hashmap.h"
|
||||
|
||||
/* start with 4 buckets */
|
||||
#define HASHMAP_MIN_CAP_BITS 2
|
||||
|
||||
static void hashmap_add_entry(struct hashmap_entry **pprev,
|
||||
struct hashmap_entry *entry)
|
||||
{
|
||||
entry->next = *pprev;
|
||||
*pprev = entry;
|
||||
}
|
||||
|
||||
static void hashmap_del_entry(struct hashmap_entry **pprev,
|
||||
struct hashmap_entry *entry)
|
||||
{
|
||||
*pprev = entry->next;
|
||||
entry->next = NULL;
|
||||
}
|
||||
|
||||
void hashmap__init(struct hashmap *map, hashmap_hash_fn hash_fn,
|
||||
hashmap_equal_fn equal_fn, void *ctx)
|
||||
{
|
||||
map->hash_fn = hash_fn;
|
||||
map->equal_fn = equal_fn;
|
||||
map->ctx = ctx;
|
||||
|
||||
map->buckets = NULL;
|
||||
map->cap = 0;
|
||||
map->cap_bits = 0;
|
||||
map->sz = 0;
|
||||
}
|
||||
|
||||
struct hashmap *hashmap__new(hashmap_hash_fn hash_fn,
|
||||
hashmap_equal_fn equal_fn,
|
||||
void *ctx)
|
||||
{
|
||||
struct hashmap *map = malloc(sizeof(struct hashmap));
|
||||
|
||||
if (!map)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
hashmap__init(map, hash_fn, equal_fn, ctx);
|
||||
return map;
|
||||
}
|
||||
|
||||
void hashmap__clear(struct hashmap *map)
|
||||
{
|
||||
free(map->buckets);
|
||||
map->cap = map->cap_bits = map->sz = 0;
|
||||
}
|
||||
|
||||
void hashmap__free(struct hashmap *map)
|
||||
{
|
||||
if (!map)
|
||||
return;
|
||||
|
||||
hashmap__clear(map);
|
||||
free(map);
|
||||
}
|
||||
|
||||
size_t hashmap__size(const struct hashmap *map)
|
||||
{
|
||||
return map->sz;
|
||||
}
|
||||
|
||||
size_t hashmap__capacity(const struct hashmap *map)
|
||||
{
|
||||
return map->cap;
|
||||
}
|
||||
|
||||
static bool hashmap_needs_to_grow(struct hashmap *map)
|
||||
{
|
||||
/* grow if empty or more than 75% filled */
|
||||
return (map->cap == 0) || ((map->sz + 1) * 4 / 3 > map->cap);
|
||||
}
|
||||
|
||||
static int hashmap_grow(struct hashmap *map)
|
||||
{
|
||||
struct hashmap_entry **new_buckets;
|
||||
struct hashmap_entry *cur, *tmp;
|
||||
size_t new_cap_bits, new_cap;
|
||||
size_t h;
|
||||
int bkt;
|
||||
|
||||
new_cap_bits = map->cap_bits + 1;
|
||||
if (new_cap_bits < HASHMAP_MIN_CAP_BITS)
|
||||
new_cap_bits = HASHMAP_MIN_CAP_BITS;
|
||||
|
||||
new_cap = 1UL << new_cap_bits;
|
||||
new_buckets = calloc(new_cap, sizeof(new_buckets[0]));
|
||||
if (!new_buckets)
|
||||
return -ENOMEM;
|
||||
|
||||
hashmap__for_each_entry_safe(map, cur, tmp, bkt) {
|
||||
h = hash_bits(map->hash_fn(cur->key, map->ctx), new_cap_bits);
|
||||
hashmap_add_entry(&new_buckets[h], cur);
|
||||
}
|
||||
|
||||
map->cap = new_cap;
|
||||
map->cap_bits = new_cap_bits;
|
||||
free(map->buckets);
|
||||
map->buckets = new_buckets;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool hashmap_find_entry(const struct hashmap *map,
|
||||
const void *key, size_t hash,
|
||||
struct hashmap_entry ***pprev,
|
||||
struct hashmap_entry **entry)
|
||||
{
|
||||
struct hashmap_entry *cur, **prev_ptr;
|
||||
|
||||
if (!map->buckets)
|
||||
return false;
|
||||
|
||||
for (prev_ptr = &map->buckets[hash], cur = *prev_ptr;
|
||||
cur;
|
||||
prev_ptr = &cur->next, cur = cur->next) {
|
||||
if (map->equal_fn(cur->key, key, map->ctx)) {
|
||||
if (pprev)
|
||||
*pprev = prev_ptr;
|
||||
*entry = cur;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int hashmap__insert(struct hashmap *map, const void *key, void *value,
|
||||
enum hashmap_insert_strategy strategy,
|
||||
const void **old_key, void **old_value)
|
||||
{
|
||||
struct hashmap_entry *entry;
|
||||
size_t h;
|
||||
int err;
|
||||
|
||||
if (old_key)
|
||||
*old_key = NULL;
|
||||
if (old_value)
|
||||
*old_value = NULL;
|
||||
|
||||
h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
|
||||
if (strategy != HASHMAP_APPEND &&
|
||||
hashmap_find_entry(map, key, h, NULL, &entry)) {
|
||||
if (old_key)
|
||||
*old_key = entry->key;
|
||||
if (old_value)
|
||||
*old_value = entry->value;
|
||||
|
||||
if (strategy == HASHMAP_SET || strategy == HASHMAP_UPDATE) {
|
||||
entry->key = key;
|
||||
entry->value = value;
|
||||
return 0;
|
||||
} else if (strategy == HASHMAP_ADD) {
|
||||
return -EEXIST;
|
||||
}
|
||||
}
|
||||
|
||||
if (strategy == HASHMAP_UPDATE)
|
||||
return -ENOENT;
|
||||
|
||||
if (hashmap_needs_to_grow(map)) {
|
||||
err = hashmap_grow(map);
|
||||
if (err)
|
||||
return err;
|
||||
h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
|
||||
}
|
||||
|
||||
entry = malloc(sizeof(struct hashmap_entry));
|
||||
if (!entry)
|
||||
return -ENOMEM;
|
||||
|
||||
entry->key = key;
|
||||
entry->value = value;
|
||||
hashmap_add_entry(&map->buckets[h], entry);
|
||||
map->sz++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool hashmap__find(const struct hashmap *map, const void *key, void **value)
|
||||
{
|
||||
struct hashmap_entry *entry;
|
||||
size_t h;
|
||||
|
||||
h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
|
||||
if (!hashmap_find_entry(map, key, h, NULL, &entry))
|
||||
return false;
|
||||
|
||||
if (value)
|
||||
*value = entry->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hashmap__delete(struct hashmap *map, const void *key,
|
||||
const void **old_key, void **old_value)
|
||||
{
|
||||
struct hashmap_entry **pprev, *entry;
|
||||
size_t h;
|
||||
|
||||
h = hash_bits(map->hash_fn(key, map->ctx), map->cap_bits);
|
||||
if (!hashmap_find_entry(map, key, h, &pprev, &entry))
|
||||
return false;
|
||||
|
||||
if (old_key)
|
||||
*old_key = entry->key;
|
||||
if (old_value)
|
||||
*old_value = entry->value;
|
||||
|
||||
hashmap_del_entry(pprev, entry);
|
||||
free(entry);
|
||||
map->sz--;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
173
tools/lib/bpf/hashmap.h
Normal file
173
tools/lib/bpf/hashmap.h
Normal file
@ -0,0 +1,173 @@
|
||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
|
||||
/*
|
||||
* Generic non-thread safe hash map implementation.
|
||||
*
|
||||
* Copyright (c) 2019 Facebook
|
||||
*/
|
||||
#ifndef __LIBBPF_HASHMAP_H
|
||||
#define __LIBBPF_HASHMAP_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include "libbpf_internal.h"
|
||||
|
||||
static inline size_t hash_bits(size_t h, int bits)
|
||||
{
|
||||
/* shuffle bits and return requested number of upper bits */
|
||||
return (h * 11400714819323198485llu) >> (__WORDSIZE - bits);
|
||||
}
|
||||
|
||||
typedef size_t (*hashmap_hash_fn)(const void *key, void *ctx);
|
||||
typedef bool (*hashmap_equal_fn)(const void *key1, const void *key2, void *ctx);
|
||||
|
||||
struct hashmap_entry {
|
||||
const void *key;
|
||||
void *value;
|
||||
struct hashmap_entry *next;
|
||||
};
|
||||
|
||||
struct hashmap {
|
||||
hashmap_hash_fn hash_fn;
|
||||
hashmap_equal_fn equal_fn;
|
||||
void *ctx;
|
||||
|
||||
struct hashmap_entry **buckets;
|
||||
size_t cap;
|
||||
size_t cap_bits;
|
||||
size_t sz;
|
||||
};
|
||||
|
||||
#define HASHMAP_INIT(hash_fn, equal_fn, ctx) { \
|
||||
.hash_fn = (hash_fn), \
|
||||
.equal_fn = (equal_fn), \
|
||||
.ctx = (ctx), \
|
||||
.buckets = NULL, \
|
||||
.cap = 0, \
|
||||
.cap_bits = 0, \
|
||||
.sz = 0, \
|
||||
}
|
||||
|
||||
void hashmap__init(struct hashmap *map, hashmap_hash_fn hash_fn,
|
||||
hashmap_equal_fn equal_fn, void *ctx);
|
||||
struct hashmap *hashmap__new(hashmap_hash_fn hash_fn,
|
||||
hashmap_equal_fn equal_fn,
|
||||
void *ctx);
|
||||
void hashmap__clear(struct hashmap *map);
|
||||
void hashmap__free(struct hashmap *map);
|
||||
|
||||
size_t hashmap__size(const struct hashmap *map);
|
||||
size_t hashmap__capacity(const struct hashmap *map);
|
||||
|
||||
/*
|
||||
* Hashmap insertion strategy:
|
||||
* - HASHMAP_ADD - only add key/value if key doesn't exist yet;
|
||||
* - HASHMAP_SET - add key/value pair if key doesn't exist yet; otherwise,
|
||||
* update value;
|
||||
* - HASHMAP_UPDATE - update value, if key already exists; otherwise, do
|
||||
* nothing and return -ENOENT;
|
||||
* - HASHMAP_APPEND - always add key/value pair, even if key already exists.
|
||||
* This turns hashmap into a multimap by allowing multiple values to be
|
||||
* associated with the same key. Most useful read API for such hashmap is
|
||||
* hashmap__for_each_key_entry() iteration. If hashmap__find() is still
|
||||
* used, it will return last inserted key/value entry (first in a bucket
|
||||
* chain).
|
||||
*/
|
||||
enum hashmap_insert_strategy {
|
||||
HASHMAP_ADD,
|
||||
HASHMAP_SET,
|
||||
HASHMAP_UPDATE,
|
||||
HASHMAP_APPEND,
|
||||
};
|
||||
|
||||
/*
|
||||
* hashmap__insert() adds key/value entry w/ various semantics, depending on
|
||||
* provided strategy value. If a given key/value pair replaced already
|
||||
* existing key/value pair, both old key and old value will be returned
|
||||
* through old_key and old_value to allow calling code do proper memory
|
||||
* management.
|
||||
*/
|
||||
int hashmap__insert(struct hashmap *map, const void *key, void *value,
|
||||
enum hashmap_insert_strategy strategy,
|
||||
const void **old_key, void **old_value);
|
||||
|
||||
static inline int hashmap__add(struct hashmap *map,
|
||||
const void *key, void *value)
|
||||
{
|
||||
return hashmap__insert(map, key, value, HASHMAP_ADD, NULL, NULL);
|
||||
}
|
||||
|
||||
static inline int hashmap__set(struct hashmap *map,
|
||||
const void *key, void *value,
|
||||
const void **old_key, void **old_value)
|
||||
{
|
||||
return hashmap__insert(map, key, value, HASHMAP_SET,
|
||||
old_key, old_value);
|
||||
}
|
||||
|
||||
static inline int hashmap__update(struct hashmap *map,
|
||||
const void *key, void *value,
|
||||
const void **old_key, void **old_value)
|
||||
{
|
||||
return hashmap__insert(map, key, value, HASHMAP_UPDATE,
|
||||
old_key, old_value);
|
||||
}
|
||||
|
||||
static inline int hashmap__append(struct hashmap *map,
|
||||
const void *key, void *value)
|
||||
{
|
||||
return hashmap__insert(map, key, value, HASHMAP_APPEND, NULL, NULL);
|
||||
}
|
||||
|
||||
bool hashmap__delete(struct hashmap *map, const void *key,
|
||||
const void **old_key, void **old_value);
|
||||
|
||||
bool hashmap__find(const struct hashmap *map, const void *key, void **value);
|
||||
|
||||
/*
|
||||
* hashmap__for_each_entry - iterate over all entries in hashmap
|
||||
* @map: hashmap to iterate
|
||||
* @cur: struct hashmap_entry * used as a loop cursor
|
||||
* @bkt: integer used as a bucket loop cursor
|
||||
*/
|
||||
#define hashmap__for_each_entry(map, cur, bkt) \
|
||||
for (bkt = 0; bkt < map->cap; bkt++) \
|
||||
for (cur = map->buckets[bkt]; cur; cur = cur->next)
|
||||
|
||||
/*
|
||||
* hashmap__for_each_entry_safe - iterate over all entries in hashmap, safe
|
||||
* against removals
|
||||
* @map: hashmap to iterate
|
||||
* @cur: struct hashmap_entry * used as a loop cursor
|
||||
* @tmp: struct hashmap_entry * used as a temporary next cursor storage
|
||||
* @bkt: integer used as a bucket loop cursor
|
||||
*/
|
||||
#define hashmap__for_each_entry_safe(map, cur, tmp, bkt) \
|
||||
for (bkt = 0; bkt < map->cap; bkt++) \
|
||||
for (cur = map->buckets[bkt]; \
|
||||
cur && ({tmp = cur->next; true; }); \
|
||||
cur = tmp)
|
||||
|
||||
/*
|
||||
* hashmap__for_each_key_entry - iterate over entries associated with given key
|
||||
* @map: hashmap to iterate
|
||||
* @cur: struct hashmap_entry * used as a loop cursor
|
||||
* @key: key to iterate entries for
|
||||
*/
|
||||
#define hashmap__for_each_key_entry(map, cur, _key) \
|
||||
for (cur = ({ size_t bkt = hash_bits(map->hash_fn((_key), map->ctx),\
|
||||
map->cap_bits); \
|
||||
map->buckets ? map->buckets[bkt] : NULL; }); \
|
||||
cur; \
|
||||
cur = cur->next) \
|
||||
if (map->equal_fn(cur->key, (_key), map->ctx))
|
||||
|
||||
#define hashmap__for_each_key_entry_safe(map, cur, tmp, _key) \
|
||||
for (cur = ({ size_t bkt = hash_bits(map->hash_fn((_key), map->ctx),\
|
||||
map->cap_bits); \
|
||||
cur = map->buckets ? map->buckets[bkt] : NULL; }); \
|
||||
cur && ({ tmp = cur->next; true; }); \
|
||||
cur = tmp) \
|
||||
if (map->equal_fn(cur->key, (_key), map->ctx))
|
||||
|
||||
#endif /* __LIBBPF_HASHMAP_H */
|
@ -164,3 +164,11 @@ LIBBPF_0.0.3 {
|
||||
bpf_map_freeze;
|
||||
btf__finalize_data;
|
||||
} LIBBPF_0.0.2;
|
||||
|
||||
LIBBPF_0.0.4 {
|
||||
global:
|
||||
btf_dump__dump_type;
|
||||
btf_dump__free;
|
||||
btf_dump__new;
|
||||
btf__parse_elf;
|
||||
} LIBBPF_0.0.3;
|
||||
|
@ -9,6 +9,8 @@
|
||||
#ifndef __LIBBPF_LIBBPF_INTERNAL_H
|
||||
#define __LIBBPF_LIBBPF_INTERNAL_H
|
||||
|
||||
#include "libbpf.h"
|
||||
|
||||
#define BTF_INFO_ENC(kind, kind_flag, vlen) \
|
||||
((!!(kind_flag) << 31) | ((kind) << 24) | ((vlen) & BTF_MAX_VLEN))
|
||||
#define BTF_TYPE_ENC(name, info, size_or_type) (name), (info), (size_or_type)
|
||||
|
2
tools/testing/selftests/bpf/.gitignore
vendored
2
tools/testing/selftests/bpf/.gitignore
vendored
@ -35,3 +35,5 @@ test_sysctl
|
||||
alu32
|
||||
libbpf.pc
|
||||
libbpf.so.*
|
||||
test_hashmap
|
||||
test_btf_dump
|
||||
|
@ -23,7 +23,8 @@ TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_lpm_map test
|
||||
test_align test_verifier_log test_dev_cgroup test_tcpbpf_user \
|
||||
test_sock test_btf test_sockmap test_lirc_mode2_user get_cgroup_id_user \
|
||||
test_socket_cookie test_cgroup_storage test_select_reuseport test_section_names \
|
||||
test_netcnt test_tcpnotify_user test_sock_fields test_sysctl
|
||||
test_netcnt test_tcpnotify_user test_sock_fields test_sysctl test_hashmap \
|
||||
test_btf_dump
|
||||
|
||||
BPF_OBJ_FILES = $(patsubst %.c,%.o, $(notdir $(wildcard progs/*.c)))
|
||||
TEST_GEN_FILES = $(BPF_OBJ_FILES)
|
||||
|
@ -0,0 +1,92 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
|
||||
/*
|
||||
* BTF-to-C dumper tests for bitfield.
|
||||
*
|
||||
* Copyright (c) 2019 Facebook
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
|
||||
/* ----- START-EXPECTED-OUTPUT ----- */
|
||||
/*
|
||||
*struct bitfields_only_mixed_types {
|
||||
* int a: 3;
|
||||
* long int b: 2;
|
||||
* _Bool c: 1;
|
||||
* enum {
|
||||
* A = 0,
|
||||
* B = 1,
|
||||
* } d: 1;
|
||||
* short e: 5;
|
||||
* int: 20;
|
||||
* unsigned int f: 30;
|
||||
*};
|
||||
*
|
||||
*/
|
||||
/* ------ END-EXPECTED-OUTPUT ------ */
|
||||
|
||||
struct bitfields_only_mixed_types {
|
||||
int a: 3;
|
||||
long int b: 2;
|
||||
bool c: 1; /* it's really a _Bool type */
|
||||
enum {
|
||||
A, /* A = 0, dumper is very explicit */
|
||||
B, /* B = 1, same */
|
||||
} d: 1;
|
||||
short e: 5;
|
||||
/* 20-bit padding here */
|
||||
unsigned f: 30; /* this gets aligned on 4-byte boundary */
|
||||
};
|
||||
|
||||
/* ----- START-EXPECTED-OUTPUT ----- */
|
||||
/*
|
||||
*struct bitfield_mixed_with_others {
|
||||
* char: 4;
|
||||
* int a: 4;
|
||||
* short b;
|
||||
* long int c;
|
||||
* long int d: 8;
|
||||
* int e;
|
||||
* int f;
|
||||
*};
|
||||
*
|
||||
*/
|
||||
/* ------ END-EXPECTED-OUTPUT ------ */
|
||||
struct bitfield_mixed_with_others {
|
||||
long: 4; /* char is enough as a backing field */
|
||||
int a: 4;
|
||||
/* 8-bit implicit padding */
|
||||
short b; /* combined with previous bitfield */
|
||||
/* 4 more bytes of implicit padding */
|
||||
long c;
|
||||
long d: 8;
|
||||
/* 24 bits implicit padding */
|
||||
int e; /* combined with previous bitfield */
|
||||
int f;
|
||||
/* 4 bytes of padding */
|
||||
};
|
||||
|
||||
/* ----- START-EXPECTED-OUTPUT ----- */
|
||||
/*
|
||||
*struct bitfield_flushed {
|
||||
* int a: 4;
|
||||
* long: 60;
|
||||
* long int b: 16;
|
||||
*};
|
||||
*
|
||||
*/
|
||||
/* ------ END-EXPECTED-OUTPUT ------ */
|
||||
struct bitfield_flushed {
|
||||
int a: 4;
|
||||
long: 0; /* flush until next natural alignment boundary */
|
||||
long b: 16;
|
||||
};
|
||||
|
||||
int f(struct {
|
||||
struct bitfields_only_mixed_types _1;
|
||||
struct bitfield_mixed_with_others _2;
|
||||
struct bitfield_flushed _3;
|
||||
} *_)
|
||||
{
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
|
||||
/*
|
||||
* BTF-to-C dumper test for multi-dimensional array output.
|
||||
*
|
||||
* Copyright (c) 2019 Facebook
|
||||
*/
|
||||
/* ----- START-EXPECTED-OUTPUT ----- */
|
||||
typedef int arr_t[2];
|
||||
|
||||
typedef int multiarr_t[3][4][5];
|
||||
|
||||
typedef int *ptr_arr_t[6];
|
||||
|
||||
typedef int *ptr_multiarr_t[7][8][9][10];
|
||||
|
||||
typedef int * (*fn_ptr_arr_t[11])();
|
||||
|
||||
typedef int * (*fn_ptr_multiarr_t[12][13])();
|
||||
|
||||
struct root_struct {
|
||||
arr_t _1;
|
||||
multiarr_t _2;
|
||||
ptr_arr_t _3;
|
||||
ptr_multiarr_t _4;
|
||||
fn_ptr_arr_t _5;
|
||||
fn_ptr_multiarr_t _6;
|
||||
};
|
||||
|
||||
/* ------ END-EXPECTED-OUTPUT ------ */
|
||||
|
||||
int f(struct root_struct *s)
|
||||
{
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
|
||||
/*
|
||||
* BTF-to-C dumper test validating no name versioning happens between
|
||||
* independent C namespaces (struct/union/enum vs typedef/enum values).
|
||||
*
|
||||
* Copyright (c) 2019 Facebook
|
||||
*/
|
||||
/* ----- START-EXPECTED-OUTPUT ----- */
|
||||
struct S {
|
||||
int S;
|
||||
int U;
|
||||
};
|
||||
|
||||
typedef struct S S;
|
||||
|
||||
union U {
|
||||
int S;
|
||||
int U;
|
||||
};
|
||||
|
||||
typedef union U U;
|
||||
|
||||
enum E {
|
||||
V = 0,
|
||||
};
|
||||
|
||||
typedef enum E E;
|
||||
|
||||
struct A {};
|
||||
|
||||
union B {};
|
||||
|
||||
enum C {
|
||||
A = 1,
|
||||
B = 2,
|
||||
C = 3,
|
||||
};
|
||||
|
||||
struct X {};
|
||||
|
||||
union Y {};
|
||||
|
||||
enum Z;
|
||||
|
||||
typedef int X;
|
||||
|
||||
typedef int Y;
|
||||
|
||||
typedef int Z;
|
||||
|
||||
/*------ END-EXPECTED-OUTPUT ------ */
|
||||
|
||||
int f(struct {
|
||||
struct S _1;
|
||||
S _2;
|
||||
union U _3;
|
||||
U _4;
|
||||
enum E _5;
|
||||
E _6;
|
||||
struct A a;
|
||||
union B b;
|
||||
enum C c;
|
||||
struct X x;
|
||||
union Y y;
|
||||
enum Z *z;
|
||||
X xx;
|
||||
Y yy;
|
||||
Z zz;
|
||||
} *_)
|
||||
{
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
|
||||
/*
|
||||
* BTF-to-C dumper test for topological sorting of dependent structs.
|
||||
*
|
||||
* Copyright (c) 2019 Facebook
|
||||
*/
|
||||
/* ----- START-EXPECTED-OUTPUT ----- */
|
||||
struct s1 {};
|
||||
|
||||
struct s3;
|
||||
|
||||
struct s4;
|
||||
|
||||
struct s2 {
|
||||
struct s2 *s2;
|
||||
struct s3 *s3;
|
||||
struct s4 *s4;
|
||||
};
|
||||
|
||||
struct s3 {
|
||||
struct s1 s1;
|
||||
struct s2 s2;
|
||||
};
|
||||
|
||||
struct s4 {
|
||||
struct s1 s1;
|
||||
struct s3 s3;
|
||||
};
|
||||
|
||||
struct list_head {
|
||||
struct list_head *next;
|
||||
struct list_head *prev;
|
||||
};
|
||||
|
||||
struct hlist_node {
|
||||
struct hlist_node *next;
|
||||
struct hlist_node **pprev;
|
||||
};
|
||||
|
||||
struct hlist_head {
|
||||
struct hlist_node *first;
|
||||
};
|
||||
|
||||
struct callback_head {
|
||||
struct callback_head *next;
|
||||
void (*func)(struct callback_head *);
|
||||
};
|
||||
|
||||
struct root_struct {
|
||||
struct s4 s4;
|
||||
struct list_head l;
|
||||
struct hlist_node n;
|
||||
struct hlist_head h;
|
||||
struct callback_head cb;
|
||||
};
|
||||
|
||||
/*------ END-EXPECTED-OUTPUT ------ */
|
||||
|
||||
int f(struct root_struct *root)
|
||||
{
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
|
||||
/*
|
||||
* BTF-to-C dumper tests for struct packing determination.
|
||||
*
|
||||
* Copyright (c) 2019 Facebook
|
||||
*/
|
||||
/* ----- START-EXPECTED-OUTPUT ----- */
|
||||
struct packed_trailing_space {
|
||||
int a;
|
||||
short b;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct non_packed_trailing_space {
|
||||
int a;
|
||||
short b;
|
||||
};
|
||||
|
||||
struct packed_fields {
|
||||
short a;
|
||||
int b;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct non_packed_fields {
|
||||
short a;
|
||||
int b;
|
||||
};
|
||||
|
||||
struct nested_packed {
|
||||
char: 4;
|
||||
int a: 4;
|
||||
long int b;
|
||||
struct {
|
||||
char c;
|
||||
int d;
|
||||
} __attribute__((packed)) e;
|
||||
} __attribute__((packed));
|
||||
|
||||
union union_is_never_packed {
|
||||
int a: 4;
|
||||
char b;
|
||||
char c: 1;
|
||||
};
|
||||
|
||||
union union_does_not_need_packing {
|
||||
struct {
|
||||
long int a;
|
||||
int b;
|
||||
} __attribute__((packed));
|
||||
int c;
|
||||
};
|
||||
|
||||
union jump_code_union {
|
||||
char code[5];
|
||||
struct {
|
||||
char jump;
|
||||
int offset;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
/*------ END-EXPECTED-OUTPUT ------ */
|
||||
|
||||
int f(struct {
|
||||
struct packed_trailing_space _1;
|
||||
struct non_packed_trailing_space _2;
|
||||
struct packed_fields _3;
|
||||
struct non_packed_fields _4;
|
||||
struct nested_packed _5;
|
||||
union union_is_never_packed _6;
|
||||
union union_does_not_need_packing _7;
|
||||
union jump_code_union _8;
|
||||
} *_)
|
||||
{
|
||||
return 0;
|
||||
}
|
111
tools/testing/selftests/bpf/progs/btf_dump_test_case_padding.c
Normal file
111
tools/testing/selftests/bpf/progs/btf_dump_test_case_padding.c
Normal file
@ -0,0 +1,111 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
|
||||
/*
|
||||
* BTF-to-C dumper tests for implicit and explicit padding between fields and
|
||||
* at the end of a struct.
|
||||
*
|
||||
* Copyright (c) 2019 Facebook
|
||||
*/
|
||||
/* ----- START-EXPECTED-OUTPUT ----- */
|
||||
struct padded_implicitly {
|
||||
int a;
|
||||
long int b;
|
||||
char c;
|
||||
};
|
||||
|
||||
/* ------ END-EXPECTED-OUTPUT ------ */
|
||||
|
||||
/* ----- START-EXPECTED-OUTPUT ----- */
|
||||
/*
|
||||
*struct padded_explicitly {
|
||||
* int a;
|
||||
* int: 32;
|
||||
* int b;
|
||||
*};
|
||||
*
|
||||
*/
|
||||
/* ------ END-EXPECTED-OUTPUT ------ */
|
||||
|
||||
struct padded_explicitly {
|
||||
int a;
|
||||
int: 1; /* algo will explicitly pad with full 32 bits here */
|
||||
int b;
|
||||
};
|
||||
|
||||
/* ----- START-EXPECTED-OUTPUT ----- */
|
||||
/*
|
||||
*struct padded_a_lot {
|
||||
* int a;
|
||||
* long: 32;
|
||||
* long: 64;
|
||||
* long: 64;
|
||||
* int b;
|
||||
*};
|
||||
*
|
||||
*/
|
||||
/* ------ END-EXPECTED-OUTPUT ------ */
|
||||
|
||||
struct padded_a_lot {
|
||||
int a;
|
||||
/* 32 bit of implicit padding here, which algo will make explicit */
|
||||
long: 64;
|
||||
long: 64;
|
||||
int b;
|
||||
};
|
||||
|
||||
/* ----- START-EXPECTED-OUTPUT ----- */
|
||||
/*
|
||||
*struct padded_cache_line {
|
||||
* int a;
|
||||
* long: 32;
|
||||
* long: 64;
|
||||
* long: 64;
|
||||
* long: 64;
|
||||
* int b;
|
||||
*};
|
||||
*
|
||||
*/
|
||||
/* ------ END-EXPECTED-OUTPUT ------ */
|
||||
|
||||
struct padded_cache_line {
|
||||
int a;
|
||||
int b __attribute__((aligned(32)));
|
||||
};
|
||||
|
||||
/* ----- START-EXPECTED-OUTPUT ----- */
|
||||
/*
|
||||
*struct zone_padding {
|
||||
* char x[0];
|
||||
*};
|
||||
*
|
||||
*struct zone {
|
||||
* int a;
|
||||
* short b;
|
||||
* short: 16;
|
||||
* struct zone_padding __pad__;
|
||||
*};
|
||||
*
|
||||
*/
|
||||
/* ------ END-EXPECTED-OUTPUT ------ */
|
||||
|
||||
struct zone_padding {
|
||||
char x[0];
|
||||
} __attribute__((__aligned__(8)));
|
||||
|
||||
struct zone {
|
||||
int a;
|
||||
short b;
|
||||
short: 16;
|
||||
struct zone_padding __pad__;
|
||||
};
|
||||
|
||||
int f(struct {
|
||||
struct padded_implicitly _1;
|
||||
struct padded_explicitly _2;
|
||||
struct padded_a_lot _3;
|
||||
struct padded_cache_line _4;
|
||||
struct zone _5;
|
||||
} *_)
|
||||
{
|
||||
return 0;
|
||||
}
|
229
tools/testing/selftests/bpf/progs/btf_dump_test_case_syntax.c
Normal file
229
tools/testing/selftests/bpf/progs/btf_dump_test_case_syntax.c
Normal file
@ -0,0 +1,229 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
|
||||
/*
|
||||
* BTF-to-C dumper test for majority of C syntax quirks.
|
||||
*
|
||||
* Copyright (c) 2019 Facebook
|
||||
*/
|
||||
/* ----- START-EXPECTED-OUTPUT ----- */
|
||||
enum e1 {
|
||||
A = 0,
|
||||
B = 1,
|
||||
};
|
||||
|
||||
enum e2 {
|
||||
C = 100,
|
||||
D = -100,
|
||||
E = 0,
|
||||
};
|
||||
|
||||
typedef enum e2 e2_t;
|
||||
|
||||
typedef enum {
|
||||
F = 0,
|
||||
G = 1,
|
||||
H = 2,
|
||||
} e3_t;
|
||||
|
||||
typedef int int_t;
|
||||
|
||||
typedef volatile const int * volatile const crazy_ptr_t;
|
||||
|
||||
typedef int *****we_need_to_go_deeper_ptr_t;
|
||||
|
||||
typedef volatile const we_need_to_go_deeper_ptr_t * restrict * volatile * const * restrict volatile * restrict const * volatile const * restrict volatile const how_about_this_ptr_t;
|
||||
|
||||
typedef int *ptr_arr_t[10];
|
||||
|
||||
typedef void (*fn_ptr1_t)(int);
|
||||
|
||||
typedef void (*printf_fn_t)(const char *, ...);
|
||||
|
||||
/* ------ END-EXPECTED-OUTPUT ------ */
|
||||
/*
|
||||
* While previous function pointers are pretty trivial (C-syntax-level
|
||||
* trivial), the following are deciphered here for future generations:
|
||||
*
|
||||
* - `fn_ptr2_t`: function, taking anonymous struct as a first arg and pointer
|
||||
* to a function, that takes int and returns int, as a second arg; returning
|
||||
* a pointer to a const pointer to a char. Equivalent to:
|
||||
* typedef struct { int a; } s_t;
|
||||
* typedef int (*fn_t)(int);
|
||||
* typedef char * const * (*fn_ptr2_t)(s_t, fn_t);
|
||||
*
|
||||
* - `fn_complext_t`: pointer to a function returning struct and accepting
|
||||
* union and struct. All structs and enum are anonymous and defined inline.
|
||||
*
|
||||
* - `signal_t: pointer to a function accepting a pointer to a function as an
|
||||
* argument and returning pointer to a function as a result. Sane equivalent:
|
||||
* typedef void (*signal_handler_t)(int);
|
||||
* typedef signal_handler_t (*signal_ptr_t)(int, signal_handler_t);
|
||||
*
|
||||
* - fn_ptr_arr1_t: array of pointers to a function accepting pointer to
|
||||
* a pointer to an int and returning pointer to a char. Easy.
|
||||
*
|
||||
* - fn_ptr_arr2_t: array of const pointers to a function taking no arguments
|
||||
* and returning a const pointer to a function, that takes pointer to a
|
||||
* `int -> char *` function and returns pointer to a char. Equivalent:
|
||||
* typedef char * (*fn_input_t)(int);
|
||||
* typedef char * (*fn_output_outer_t)(fn_input_t);
|
||||
* typedef const fn_output_outer_t (* fn_output_inner_t)();
|
||||
* typedef const fn_output_inner_t fn_ptr_arr2_t[5];
|
||||
*/
|
||||
/* ----- START-EXPECTED-OUTPUT ----- */
|
||||
typedef char * const * (*fn_ptr2_t)(struct {
|
||||
int a;
|
||||
}, int (*)(int));
|
||||
|
||||
typedef struct {
|
||||
int a;
|
||||
void (*b)(int, struct {
|
||||
int c;
|
||||
}, union {
|
||||
char d;
|
||||
int e[5];
|
||||
});
|
||||
} (*fn_complex_t)(union {
|
||||
void *f;
|
||||
char g[16];
|
||||
}, struct {
|
||||
int h;
|
||||
});
|
||||
|
||||
typedef void (* (*signal_t)(int, void (*)(int)))(int);
|
||||
|
||||
typedef char * (*fn_ptr_arr1_t[10])(int **);
|
||||
|
||||
typedef char * (* const (* const fn_ptr_arr2_t[5])())(char * (*)(int));
|
||||
|
||||
struct struct_w_typedefs {
|
||||
int_t a;
|
||||
crazy_ptr_t b;
|
||||
we_need_to_go_deeper_ptr_t c;
|
||||
how_about_this_ptr_t d;
|
||||
ptr_arr_t e;
|
||||
fn_ptr1_t f;
|
||||
printf_fn_t g;
|
||||
fn_ptr2_t h;
|
||||
fn_complex_t i;
|
||||
signal_t j;
|
||||
fn_ptr_arr1_t k;
|
||||
fn_ptr_arr2_t l;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
} anon_struct_t;
|
||||
|
||||
struct struct_fwd;
|
||||
|
||||
typedef struct struct_fwd struct_fwd_t;
|
||||
|
||||
typedef struct struct_fwd *struct_fwd_ptr_t;
|
||||
|
||||
union union_fwd;
|
||||
|
||||
typedef union union_fwd union_fwd_t;
|
||||
|
||||
typedef union union_fwd *union_fwd_ptr_t;
|
||||
|
||||
struct struct_empty {};
|
||||
|
||||
struct struct_simple {
|
||||
int a;
|
||||
char b;
|
||||
const int_t *p;
|
||||
struct struct_empty s;
|
||||
enum e2 e;
|
||||
enum {
|
||||
ANON_VAL1 = 1,
|
||||
ANON_VAL2 = 2,
|
||||
} f;
|
||||
int arr1[13];
|
||||
enum e2 arr2[5];
|
||||
};
|
||||
|
||||
union union_empty {};
|
||||
|
||||
union union_simple {
|
||||
void *ptr;
|
||||
int num;
|
||||
int_t num2;
|
||||
union union_empty u;
|
||||
};
|
||||
|
||||
struct struct_in_struct {
|
||||
struct struct_simple simple;
|
||||
union union_simple also_simple;
|
||||
struct {
|
||||
int a;
|
||||
} not_so_hard_as_well;
|
||||
union {
|
||||
int b;
|
||||
int c;
|
||||
} anon_union_is_good;
|
||||
struct {
|
||||
int d;
|
||||
int e;
|
||||
};
|
||||
union {
|
||||
int f;
|
||||
int g;
|
||||
};
|
||||
};
|
||||
|
||||
struct struct_with_embedded_stuff {
|
||||
int a;
|
||||
struct {
|
||||
int b;
|
||||
struct {
|
||||
struct struct_with_embedded_stuff *c;
|
||||
const char *d;
|
||||
} e;
|
||||
union {
|
||||
volatile long int f;
|
||||
void * restrict g;
|
||||
};
|
||||
};
|
||||
union {
|
||||
const int_t *h;
|
||||
void (*i)(char, int, void *);
|
||||
} j;
|
||||
enum {
|
||||
K = 100,
|
||||
L = 200,
|
||||
} m;
|
||||
char n[16];
|
||||
struct {
|
||||
char o;
|
||||
int p;
|
||||
void (*q)(int);
|
||||
} r[5];
|
||||
struct struct_in_struct s[10];
|
||||
int t[11];
|
||||
};
|
||||
|
||||
struct root_struct {
|
||||
enum e1 _1;
|
||||
enum e2 _2;
|
||||
e2_t _2_1;
|
||||
e3_t _2_2;
|
||||
struct struct_w_typedefs _3;
|
||||
anon_struct_t _7;
|
||||
struct struct_fwd *_8;
|
||||
struct_fwd_t *_9;
|
||||
struct_fwd_ptr_t _10;
|
||||
union union_fwd *_11;
|
||||
union_fwd_t *_12;
|
||||
union_fwd_ptr_t _13;
|
||||
struct struct_with_embedded_stuff _14;
|
||||
};
|
||||
|
||||
/* ------ END-EXPECTED-OUTPUT ------ */
|
||||
|
||||
int f(struct root_struct *s)
|
||||
{
|
||||
return 0;
|
||||
}
|
@ -4025,62 +4025,13 @@ static struct btf_file_test file_tests[] = {
|
||||
},
|
||||
};
|
||||
|
||||
static int file_has_btf_elf(const char *fn, bool *has_btf_ext)
|
||||
{
|
||||
Elf_Scn *scn = NULL;
|
||||
GElf_Ehdr ehdr;
|
||||
int ret = 0;
|
||||
int elf_fd;
|
||||
Elf *elf;
|
||||
|
||||
if (CHECK(elf_version(EV_CURRENT) == EV_NONE,
|
||||
"elf_version(EV_CURRENT) == EV_NONE"))
|
||||
return -1;
|
||||
|
||||
elf_fd = open(fn, O_RDONLY);
|
||||
if (CHECK(elf_fd == -1, "open(%s): errno:%d", fn, errno))
|
||||
return -1;
|
||||
|
||||
elf = elf_begin(elf_fd, ELF_C_READ, NULL);
|
||||
if (CHECK(!elf, "elf_begin(%s): %s", fn, elf_errmsg(elf_errno()))) {
|
||||
ret = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (CHECK(!gelf_getehdr(elf, &ehdr), "!gelf_getehdr(%s)", fn)) {
|
||||
ret = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
while ((scn = elf_nextscn(elf, scn))) {
|
||||
const char *sh_name;
|
||||
GElf_Shdr sh;
|
||||
|
||||
if (CHECK(gelf_getshdr(scn, &sh) != &sh,
|
||||
"file:%s gelf_getshdr != &sh", fn)) {
|
||||
ret = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
sh_name = elf_strptr(elf, ehdr.e_shstrndx, sh.sh_name);
|
||||
if (!strcmp(sh_name, BTF_ELF_SEC))
|
||||
ret = 1;
|
||||
if (!strcmp(sh_name, BTF_EXT_ELF_SEC))
|
||||
*has_btf_ext = true;
|
||||
}
|
||||
|
||||
done:
|
||||
close(elf_fd);
|
||||
elf_end(elf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int do_test_file(unsigned int test_num)
|
||||
{
|
||||
const struct btf_file_test *test = &file_tests[test_num - 1];
|
||||
const char *expected_fnames[] = {"_dummy_tracepoint",
|
||||
"test_long_fname_1",
|
||||
"test_long_fname_2"};
|
||||
struct btf_ext *btf_ext = NULL;
|
||||
struct bpf_prog_info info = {};
|
||||
struct bpf_object *obj = NULL;
|
||||
struct bpf_func_info *finfo;
|
||||
@ -4095,15 +4046,19 @@ static int do_test_file(unsigned int test_num)
|
||||
fprintf(stderr, "BTF libbpf test[%u] (%s): ", test_num,
|
||||
test->file);
|
||||
|
||||
err = file_has_btf_elf(test->file, &has_btf_ext);
|
||||
if (err == -1)
|
||||
return err;
|
||||
|
||||
if (err == 0) {
|
||||
fprintf(stderr, "SKIP. No ELF %s found", BTF_ELF_SEC);
|
||||
skip_cnt++;
|
||||
return 0;
|
||||
btf = btf__parse_elf(test->file, &btf_ext);
|
||||
if (IS_ERR(btf)) {
|
||||
if (PTR_ERR(btf) == -ENOENT) {
|
||||
fprintf(stderr, "SKIP. No ELF %s found", BTF_ELF_SEC);
|
||||
skip_cnt++;
|
||||
return 0;
|
||||
}
|
||||
return PTR_ERR(btf);
|
||||
}
|
||||
btf__free(btf);
|
||||
|
||||
has_btf_ext = btf_ext != NULL;
|
||||
btf_ext__free(btf_ext);
|
||||
|
||||
obj = bpf_object__open(test->file);
|
||||
if (CHECK(IS_ERR(obj), "obj: %ld", PTR_ERR(obj)))
|
||||
|
143
tools/testing/selftests/bpf/test_btf_dump.c
Normal file
143
tools/testing/selftests/bpf/test_btf_dump.c
Normal file
@ -0,0 +1,143 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <linux/err.h>
|
||||
#include <btf.h>
|
||||
|
||||
#define CHECK(condition, format...) ({ \
|
||||
int __ret = !!(condition); \
|
||||
if (__ret) { \
|
||||
fprintf(stderr, "%s:%d:FAIL ", __func__, __LINE__); \
|
||||
fprintf(stderr, format); \
|
||||
} \
|
||||
__ret; \
|
||||
})
|
||||
|
||||
void btf_dump_printf(void *ctx, const char *fmt, va_list args)
|
||||
{
|
||||
vfprintf(ctx, fmt, args);
|
||||
}
|
||||
|
||||
struct btf_dump_test_case {
|
||||
const char *name;
|
||||
struct btf_dump_opts opts;
|
||||
} btf_dump_test_cases[] = {
|
||||
{.name = "btf_dump_test_case_syntax", .opts = {}},
|
||||
{.name = "btf_dump_test_case_ordering", .opts = {}},
|
||||
{.name = "btf_dump_test_case_padding", .opts = {}},
|
||||
{.name = "btf_dump_test_case_packing", .opts = {}},
|
||||
{.name = "btf_dump_test_case_bitfields", .opts = {}},
|
||||
{.name = "btf_dump_test_case_multidim", .opts = {}},
|
||||
{.name = "btf_dump_test_case_namespacing", .opts = {}},
|
||||
};
|
||||
|
||||
static int btf_dump_all_types(const struct btf *btf,
|
||||
const struct btf_dump_opts *opts)
|
||||
{
|
||||
size_t type_cnt = btf__get_nr_types(btf);
|
||||
struct btf_dump *d;
|
||||
int err = 0, id;
|
||||
|
||||
d = btf_dump__new(btf, NULL, opts, btf_dump_printf);
|
||||
if (IS_ERR(d))
|
||||
return PTR_ERR(d);
|
||||
|
||||
for (id = 1; id <= type_cnt; id++) {
|
||||
err = btf_dump__dump_type(d, id);
|
||||
if (err)
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
btf_dump__free(d);
|
||||
return err;
|
||||
}
|
||||
|
||||
int test_btf_dump_case(int n, struct btf_dump_test_case *test_case)
|
||||
{
|
||||
char test_file[256], out_file[256], diff_cmd[1024];
|
||||
struct btf *btf = NULL;
|
||||
int err = 0, fd = -1;
|
||||
FILE *f = NULL;
|
||||
|
||||
fprintf(stderr, "Test case #%d (%s): ", n, test_case->name);
|
||||
|
||||
snprintf(test_file, sizeof(test_file), "%s.o", test_case->name);
|
||||
|
||||
btf = btf__parse_elf(test_file, NULL);
|
||||
if (CHECK(IS_ERR(btf),
|
||||
"failed to load test BTF: %ld\n", PTR_ERR(btf))) {
|
||||
err = -PTR_ERR(btf);
|
||||
btf = NULL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
snprintf(out_file, sizeof(out_file),
|
||||
"/tmp/%s.output.XXXXXX", test_case->name);
|
||||
fd = mkstemp(out_file);
|
||||
if (CHECK(fd < 0, "failed to create temp output file: %d\n", fd)) {
|
||||
err = fd;
|
||||
goto done;
|
||||
}
|
||||
f = fdopen(fd, "w");
|
||||
if (CHECK(f == NULL, "failed to open temp output file: %s(%d)\n",
|
||||
strerror(errno), errno)) {
|
||||
close(fd);
|
||||
goto done;
|
||||
}
|
||||
|
||||
test_case->opts.ctx = f;
|
||||
err = btf_dump_all_types(btf, &test_case->opts);
|
||||
fclose(f);
|
||||
close(fd);
|
||||
if (CHECK(err, "failure during C dumping: %d\n", err)) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
snprintf(test_file, sizeof(test_file), "progs/%s.c", test_case->name);
|
||||
/*
|
||||
* Diff test output and expected test output, contained between
|
||||
* START-EXPECTED-OUTPUT and END-EXPECTED-OUTPUT lines in test case.
|
||||
* For expected output lines, everything before '*' is stripped out.
|
||||
* Also lines containing comment start and comment end markers are
|
||||
* ignored.
|
||||
*/
|
||||
snprintf(diff_cmd, sizeof(diff_cmd),
|
||||
"awk '/START-EXPECTED-OUTPUT/{out=1;next} "
|
||||
"/END-EXPECTED-OUTPUT/{out=0} "
|
||||
"/\\/\\*|\\*\\//{next} " /* ignore comment start/end lines */
|
||||
"out {sub(/^[ \\t]*\\*/, \"\"); print}' '%s' | diff -u - '%s'",
|
||||
test_file, out_file);
|
||||
err = system(diff_cmd);
|
||||
if (CHECK(err,
|
||||
"differing test output, output=%s, err=%d, diff cmd:\n%s\n",
|
||||
out_file, err, diff_cmd))
|
||||
goto done;
|
||||
|
||||
remove(out_file);
|
||||
fprintf(stderr, "OK\n");
|
||||
|
||||
done:
|
||||
btf__free(btf);
|
||||
return err;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int test_case_cnt, i, err, failed = 0;
|
||||
|
||||
test_case_cnt = sizeof(btf_dump_test_cases) /
|
||||
sizeof(btf_dump_test_cases[0]);
|
||||
|
||||
for (i = 0; i < test_case_cnt; i++) {
|
||||
err = test_btf_dump_case(i, &btf_dump_test_cases[i]);
|
||||
if (err)
|
||||
failed++;
|
||||
}
|
||||
|
||||
fprintf(stderr, "%d tests succeeded, %d tests failed.\n",
|
||||
test_case_cnt - failed, failed);
|
||||
|
||||
return failed;
|
||||
}
|
382
tools/testing/selftests/bpf/test_hashmap.c
Normal file
382
tools/testing/selftests/bpf/test_hashmap.c
Normal file
@ -0,0 +1,382 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
|
||||
/*
|
||||
* Tests for libbpf's hashmap.
|
||||
*
|
||||
* Copyright (c) 2019 Facebook
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <linux/err.h>
|
||||
#include "hashmap.h"
|
||||
|
||||
#define CHECK(condition, format...) ({ \
|
||||
int __ret = !!(condition); \
|
||||
if (__ret) { \
|
||||
fprintf(stderr, "%s:%d:FAIL ", __func__, __LINE__); \
|
||||
fprintf(stderr, format); \
|
||||
} \
|
||||
__ret; \
|
||||
})
|
||||
|
||||
size_t hash_fn(const void *k, void *ctx)
|
||||
{
|
||||
return (long)k;
|
||||
}
|
||||
|
||||
bool equal_fn(const void *a, const void *b, void *ctx)
|
||||
{
|
||||
return (long)a == (long)b;
|
||||
}
|
||||
|
||||
static inline size_t next_pow_2(size_t n)
|
||||
{
|
||||
size_t r = 1;
|
||||
|
||||
while (r < n)
|
||||
r <<= 1;
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline size_t exp_cap(size_t sz)
|
||||
{
|
||||
size_t r = next_pow_2(sz);
|
||||
|
||||
if (sz * 4 / 3 > r)
|
||||
r <<= 1;
|
||||
return r;
|
||||
}
|
||||
|
||||
#define ELEM_CNT 62
|
||||
|
||||
int test_hashmap_generic(void)
|
||||
{
|
||||
struct hashmap_entry *entry, *tmp;
|
||||
int err, bkt, found_cnt, i;
|
||||
long long found_msk;
|
||||
struct hashmap *map;
|
||||
|
||||
fprintf(stderr, "%s: ", __func__);
|
||||
|
||||
map = hashmap__new(hash_fn, equal_fn, NULL);
|
||||
if (CHECK(IS_ERR(map), "failed to create map: %ld\n", PTR_ERR(map)))
|
||||
return 1;
|
||||
|
||||
for (i = 0; i < ELEM_CNT; i++) {
|
||||
const void *oldk, *k = (const void *)(long)i;
|
||||
void *oldv, *v = (void *)(long)(1024 + i);
|
||||
|
||||
err = hashmap__update(map, k, v, &oldk, &oldv);
|
||||
if (CHECK(err != -ENOENT, "unexpected result: %d\n", err))
|
||||
return 1;
|
||||
|
||||
if (i % 2) {
|
||||
err = hashmap__add(map, k, v);
|
||||
} else {
|
||||
err = hashmap__set(map, k, v, &oldk, &oldv);
|
||||
if (CHECK(oldk != NULL || oldv != NULL,
|
||||
"unexpected k/v: %p=%p\n", oldk, oldv))
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (CHECK(err, "failed to add k/v %ld = %ld: %d\n",
|
||||
(long)k, (long)v, err))
|
||||
return 1;
|
||||
|
||||
if (CHECK(!hashmap__find(map, k, &oldv),
|
||||
"failed to find key %ld\n", (long)k))
|
||||
return 1;
|
||||
if (CHECK(oldv != v, "found value is wrong: %ld\n", (long)oldv))
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (CHECK(hashmap__size(map) != ELEM_CNT,
|
||||
"invalid map size: %zu\n", hashmap__size(map)))
|
||||
return 1;
|
||||
if (CHECK(hashmap__capacity(map) != exp_cap(hashmap__size(map)),
|
||||
"unexpected map capacity: %zu\n", hashmap__capacity(map)))
|
||||
return 1;
|
||||
|
||||
found_msk = 0;
|
||||
hashmap__for_each_entry(map, entry, bkt) {
|
||||
long k = (long)entry->key;
|
||||
long v = (long)entry->value;
|
||||
|
||||
found_msk |= 1ULL << k;
|
||||
if (CHECK(v - k != 1024, "invalid k/v pair: %ld = %ld\n", k, v))
|
||||
return 1;
|
||||
}
|
||||
if (CHECK(found_msk != (1ULL << ELEM_CNT) - 1,
|
||||
"not all keys iterated: %llx\n", found_msk))
|
||||
return 1;
|
||||
|
||||
for (i = 0; i < ELEM_CNT; i++) {
|
||||
const void *oldk, *k = (const void *)(long)i;
|
||||
void *oldv, *v = (void *)(long)(256 + i);
|
||||
|
||||
err = hashmap__add(map, k, v);
|
||||
if (CHECK(err != -EEXIST, "unexpected add result: %d\n", err))
|
||||
return 1;
|
||||
|
||||
if (i % 2)
|
||||
err = hashmap__update(map, k, v, &oldk, &oldv);
|
||||
else
|
||||
err = hashmap__set(map, k, v, &oldk, &oldv);
|
||||
|
||||
if (CHECK(err, "failed to update k/v %ld = %ld: %d\n",
|
||||
(long)k, (long)v, err))
|
||||
return 1;
|
||||
if (CHECK(!hashmap__find(map, k, &oldv),
|
||||
"failed to find key %ld\n", (long)k))
|
||||
return 1;
|
||||
if (CHECK(oldv != v, "found value is wrong: %ld\n", (long)oldv))
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (CHECK(hashmap__size(map) != ELEM_CNT,
|
||||
"invalid updated map size: %zu\n", hashmap__size(map)))
|
||||
return 1;
|
||||
if (CHECK(hashmap__capacity(map) != exp_cap(hashmap__size(map)),
|
||||
"unexpected map capacity: %zu\n", hashmap__capacity(map)))
|
||||
return 1;
|
||||
|
||||
found_msk = 0;
|
||||
hashmap__for_each_entry_safe(map, entry, tmp, bkt) {
|
||||
long k = (long)entry->key;
|
||||
long v = (long)entry->value;
|
||||
|
||||
found_msk |= 1ULL << k;
|
||||
if (CHECK(v - k != 256,
|
||||
"invalid updated k/v pair: %ld = %ld\n", k, v))
|
||||
return 1;
|
||||
}
|
||||
if (CHECK(found_msk != (1ULL << ELEM_CNT) - 1,
|
||||
"not all keys iterated after update: %llx\n", found_msk))
|
||||
return 1;
|
||||
|
||||
found_cnt = 0;
|
||||
hashmap__for_each_key_entry(map, entry, (void *)0) {
|
||||
found_cnt++;
|
||||
}
|
||||
if (CHECK(!found_cnt, "didn't find any entries for key 0\n"))
|
||||
return 1;
|
||||
|
||||
found_msk = 0;
|
||||
found_cnt = 0;
|
||||
hashmap__for_each_key_entry_safe(map, entry, tmp, (void *)0) {
|
||||
const void *oldk, *k;
|
||||
void *oldv, *v;
|
||||
|
||||
k = entry->key;
|
||||
v = entry->value;
|
||||
|
||||
found_cnt++;
|
||||
found_msk |= 1ULL << (long)k;
|
||||
|
||||
if (CHECK(!hashmap__delete(map, k, &oldk, &oldv),
|
||||
"failed to delete k/v %ld = %ld\n",
|
||||
(long)k, (long)v))
|
||||
return 1;
|
||||
if (CHECK(oldk != k || oldv != v,
|
||||
"invalid deleted k/v: expected %ld = %ld, got %ld = %ld\n",
|
||||
(long)k, (long)v, (long)oldk, (long)oldv))
|
||||
return 1;
|
||||
if (CHECK(hashmap__delete(map, k, &oldk, &oldv),
|
||||
"unexpectedly deleted k/v %ld = %ld\n",
|
||||
(long)oldk, (long)oldv))
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (CHECK(!found_cnt || !found_msk,
|
||||
"didn't delete any key entries\n"))
|
||||
return 1;
|
||||
if (CHECK(hashmap__size(map) != ELEM_CNT - found_cnt,
|
||||
"invalid updated map size (already deleted: %d): %zu\n",
|
||||
found_cnt, hashmap__size(map)))
|
||||
return 1;
|
||||
if (CHECK(hashmap__capacity(map) != exp_cap(hashmap__size(map)),
|
||||
"unexpected map capacity: %zu\n", hashmap__capacity(map)))
|
||||
return 1;
|
||||
|
||||
hashmap__for_each_entry_safe(map, entry, tmp, bkt) {
|
||||
const void *oldk, *k;
|
||||
void *oldv, *v;
|
||||
|
||||
k = entry->key;
|
||||
v = entry->value;
|
||||
|
||||
found_cnt++;
|
||||
found_msk |= 1ULL << (long)k;
|
||||
|
||||
if (CHECK(!hashmap__delete(map, k, &oldk, &oldv),
|
||||
"failed to delete k/v %ld = %ld\n",
|
||||
(long)k, (long)v))
|
||||
return 1;
|
||||
if (CHECK(oldk != k || oldv != v,
|
||||
"invalid old k/v: expect %ld = %ld, got %ld = %ld\n",
|
||||
(long)k, (long)v, (long)oldk, (long)oldv))
|
||||
return 1;
|
||||
if (CHECK(hashmap__delete(map, k, &oldk, &oldv),
|
||||
"unexpectedly deleted k/v %ld = %ld\n",
|
||||
(long)k, (long)v))
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (CHECK(found_cnt != ELEM_CNT || found_msk != (1ULL << ELEM_CNT) - 1,
|
||||
"not all keys were deleted: found_cnt:%d, found_msk:%llx\n",
|
||||
found_cnt, found_msk))
|
||||
return 1;
|
||||
if (CHECK(hashmap__size(map) != 0,
|
||||
"invalid updated map size (already deleted: %d): %zu\n",
|
||||
found_cnt, hashmap__size(map)))
|
||||
return 1;
|
||||
|
||||
found_cnt = 0;
|
||||
hashmap__for_each_entry(map, entry, bkt) {
|
||||
CHECK(false, "unexpected map entries left: %ld = %ld\n",
|
||||
(long)entry->key, (long)entry->value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
hashmap__free(map);
|
||||
hashmap__for_each_entry(map, entry, bkt) {
|
||||
CHECK(false, "unexpected map entries left: %ld = %ld\n",
|
||||
(long)entry->key, (long)entry->value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "OK\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t collision_hash_fn(const void *k, void *ctx)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_hashmap_multimap(void)
|
||||
{
|
||||
void *k1 = (void *)0, *k2 = (void *)1;
|
||||
struct hashmap_entry *entry;
|
||||
struct hashmap *map;
|
||||
long found_msk;
|
||||
int err, bkt;
|
||||
|
||||
fprintf(stderr, "%s: ", __func__);
|
||||
|
||||
/* force collisions */
|
||||
map = hashmap__new(collision_hash_fn, equal_fn, NULL);
|
||||
if (CHECK(IS_ERR(map), "failed to create map: %ld\n", PTR_ERR(map)))
|
||||
return 1;
|
||||
|
||||
|
||||
/* set up multimap:
|
||||
* [0] -> 1, 2, 4;
|
||||
* [1] -> 8, 16, 32;
|
||||
*/
|
||||
err = hashmap__append(map, k1, (void *)1);
|
||||
if (CHECK(err, "failed to add k/v: %d\n", err))
|
||||
return 1;
|
||||
err = hashmap__append(map, k1, (void *)2);
|
||||
if (CHECK(err, "failed to add k/v: %d\n", err))
|
||||
return 1;
|
||||
err = hashmap__append(map, k1, (void *)4);
|
||||
if (CHECK(err, "failed to add k/v: %d\n", err))
|
||||
return 1;
|
||||
|
||||
err = hashmap__append(map, k2, (void *)8);
|
||||
if (CHECK(err, "failed to add k/v: %d\n", err))
|
||||
return 1;
|
||||
err = hashmap__append(map, k2, (void *)16);
|
||||
if (CHECK(err, "failed to add k/v: %d\n", err))
|
||||
return 1;
|
||||
err = hashmap__append(map, k2, (void *)32);
|
||||
if (CHECK(err, "failed to add k/v: %d\n", err))
|
||||
return 1;
|
||||
|
||||
if (CHECK(hashmap__size(map) != 6,
|
||||
"invalid map size: %zu\n", hashmap__size(map)))
|
||||
return 1;
|
||||
|
||||
/* verify global iteration still works and sees all values */
|
||||
found_msk = 0;
|
||||
hashmap__for_each_entry(map, entry, bkt) {
|
||||
found_msk |= (long)entry->value;
|
||||
}
|
||||
if (CHECK(found_msk != (1 << 6) - 1,
|
||||
"not all keys iterated: %lx\n", found_msk))
|
||||
return 1;
|
||||
|
||||
/* iterate values for key 1 */
|
||||
found_msk = 0;
|
||||
hashmap__for_each_key_entry(map, entry, k1) {
|
||||
found_msk |= (long)entry->value;
|
||||
}
|
||||
if (CHECK(found_msk != (1 | 2 | 4),
|
||||
"invalid k1 values: %lx\n", found_msk))
|
||||
return 1;
|
||||
|
||||
/* iterate values for key 2 */
|
||||
found_msk = 0;
|
||||
hashmap__for_each_key_entry(map, entry, k2) {
|
||||
found_msk |= (long)entry->value;
|
||||
}
|
||||
if (CHECK(found_msk != (8 | 16 | 32),
|
||||
"invalid k2 values: %lx\n", found_msk))
|
||||
return 1;
|
||||
|
||||
fprintf(stderr, "OK\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_hashmap_empty()
|
||||
{
|
||||
struct hashmap_entry *entry;
|
||||
int bkt;
|
||||
struct hashmap *map;
|
||||
void *k = (void *)0;
|
||||
|
||||
fprintf(stderr, "%s: ", __func__);
|
||||
|
||||
/* force collisions */
|
||||
map = hashmap__new(hash_fn, equal_fn, NULL);
|
||||
if (CHECK(IS_ERR(map), "failed to create map: %ld\n", PTR_ERR(map)))
|
||||
return 1;
|
||||
|
||||
if (CHECK(hashmap__size(map) != 0,
|
||||
"invalid map size: %zu\n", hashmap__size(map)))
|
||||
return 1;
|
||||
if (CHECK(hashmap__capacity(map) != 0,
|
||||
"invalid map capacity: %zu\n", hashmap__capacity(map)))
|
||||
return 1;
|
||||
if (CHECK(hashmap__find(map, k, NULL), "unexpected find\n"))
|
||||
return 1;
|
||||
if (CHECK(hashmap__delete(map, k, NULL, NULL), "unexpected delete\n"))
|
||||
return 1;
|
||||
|
||||
hashmap__for_each_entry(map, entry, bkt) {
|
||||
CHECK(false, "unexpected iterated entry\n");
|
||||
return 1;
|
||||
}
|
||||
hashmap__for_each_key_entry(map, entry, k) {
|
||||
CHECK(false, "unexpected key entry\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "OK\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
bool failed = false;
|
||||
|
||||
if (test_hashmap_generic())
|
||||
failed = true;
|
||||
if (test_hashmap_multimap())
|
||||
failed = true;
|
||||
if (test_hashmap_empty())
|
||||
failed = true;
|
||||
|
||||
return failed;
|
||||
}
|
Loading…
Reference in New Issue
Block a user