BPF_ARRAY is a very common macro used in bcc scripts. To analyze it, I put all BPF_ARRAY
related macros in an example file:
// Changes to the macro require changes in BFrontendAction classes
#define BPF_F_TABLE(_table_type, _key_type, _leaf_type, _name, _max_entries, _flags) \
struct _name##_table_t { \
_key_type key; \
_leaf_type leaf; \
_leaf_type * (*lookup) (_key_type *); \
_leaf_type * (*lookup_or_init) (_key_type *, _leaf_type *); \
int (*update) (_key_type *, _leaf_type *); \
int (*insert) (_key_type *, _leaf_type *); \
int (*delete) (_key_type *); \
void (*call) (void *, int index); \
void (*increment) (_key_type); \
int (*get_stackid) (void *, u64); \
_leaf_type data[_max_entries]; \
int flags; \
}; \
__attribute__((section("maps/" _table_type))) \
struct _name##_table_t _name = { .flags = (_flags) }
#define BPF_TABLE(_table_type, _key_type, _leaf_type, _name, _max_entries) \
BPF_F_TABLE(_table_type, _key_type, _leaf_type, _name, _max_entries, 0);
#define BPF_ARRAY1(_name) \
BPF_TABLE("array", int, u64, _name, 10240)
#define BPF_ARRAY2(_name, _leaf_type) \
BPF_TABLE("array", int, _leaf_type, _name, 10240)
#define BPF_ARRAY3(_name, _leaf_type, _size) \
BPF_TABLE("array", int, _leaf_type, _name, _size)
// helper for default-variable macro function
#define BPF_ARRAYX(_1, _2, _3, NAME, ...) NAME
// Define an array function, some arguments optional
// BPF_ARRAY(name, leaf_type=u64, size=10240)
#define BPF_ARRAY(...) \
BPF_ARRAYX(__VA_ARGS__, BPF_ARRAY3, BPF_ARRAY2, BPF_ARRAY1)(__VA_ARGS__)
enum stat_types {
S_COUNT = 1,
S_MAXSTAT
};
void main(void)
{
BPF_ARRAY(stats, u64, S_MAXSTAT + 1);
}
Use gcc -E
to preprocess it (I have formatted code to make it clear):
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "test.c"
# 38 "test.c"
enum stat_types {
S_COUNT = 1,
S_MAXSTAT
};
void main(void)
{
struct stats_table_t {
int key;
u64 leaf;
u64 * (*lookup) (int *);
u64 * (*lookup_or_init) (int *, u64 *);
int (*update) (int *, u64 *);
int (*insert) (int *, u64 *);
int (*delete) (int *);
void (*call) (void *, int index);
void (*increment) (int);
int (*get_stackid) (void *, u64);
u64 data[S_MAXSTAT + 1];
int flags;
};
__attribute__((section("maps/" "array"))) struct stats_table_t stats = { .flags = (0) };;
}
Let’s analyze how the final result is got:
(1)
BPF_ARRAY(stats, u64, S_MAXSTAT + 1)
will be expanded to (__VA_ARGS__
will be replaced by all arguments):
BPF_ARRAYX(stats, u64, S_MAXSTAT + 1, BPF_ARRAY3, BPF_ARRAY2, BPF_ARRAY1)(stats, u64, S_MAXSTAT + 1)
(2) According to:
// helper for default-variable macro function
#define BPF_ARRAYX(_1, _2, _3, NAME, ...) NAME
BPF_ARRAYX(stats, u64, S_MAXSTAT + 1, BPF_ARRAY3, BPF_ARRAY2, BPF_ARRAY1)(stats, u64, S_MAXSTAT + 1)
will be replaced by following code:
BPF_ARRAY3(stats, u64, S_MAXSTAT + 1)
We can compared it to BPF_ARRAY1
(both _leaf_type
and size
have default values) and BPF_ARRAY2
(only size
have default value.).
(3) Convert BPF_ARRAY3
to final BPF_F_TABLE
is more straightforward, so I won’t drill that down.
Use the same method you can analyze other macros, such as BPF_HASH
. Hope this small post can give you a tip!