While playing with using libbpf for the Suricata project, we had
issues LLVM >= 4.0.1 generating ELF files that could not be loaded
with libbpf (tools/lib/bpf/).
During the troubleshooting phase, I wrote a test program and improved
the debugging output in libbpf. I turned this into a selftests
program, and it also serves as a code example for libbpf in itself.
I discovered that there are at least three ELF load issues with
libbpf. I left them as TODO comments in (tools/testing/selftests/bpf)
test_libbpf.sh. I've only fixed the load issue with eh_frames. We can
work on the other issues later.
---
Jesper Dangaard Brouer (5):
bpf: Sync kernel ABI header with tooling header for bpf_common.h
tools/libbpf: improve the pr_debug statements to contain section numbers
selftests/bpf: add test program for loading BPF ELF files
selftests/bpf: add selftest that use test_libbpf_open
tools/libbpf: handle issues with bpf ELF objects containing .eh_frames
tools/include/uapi/linux/bpf_common.h | 7 +
tools/lib/bpf/libbpf.c | 32 +++--
tools/testing/selftests/bpf/Makefile | 12 ++
tools/testing/selftests/bpf/test_libbpf.sh | 49 ++++++++
tools/testing/selftests/bpf/test_libbpf_open.c | 150 ++++++++++++++++++++++++
5 files changed, 234 insertions(+), 16 deletions(-)
create mode 100755 tools/testing/selftests/bpf/test_libbpf.sh
create mode 100644 tools/testing/selftests/bpf/test_libbpf_open.c
I recently fixed up a lot of commits that forgot to keep the tooling
headers in sync. And then I forgot to do the same thing in commit
cb5f7334d479 ("bpf: add comments to BPF ld/ldx sizes"). Let correct
that before people notice ;-).
Lawrence did partly fix/sync this for bpf.h in commit d6d4f60c3a09
("bpf: add selftest for tcpbpf").
Fixes: cb5f7334d479 ("bpf: add comments to BPF ld/ldx sizes")
Signed-off-by: Jesper Dangaard Brouer <redacted>
---
tools/include/uapi/linux/bpf_common.h | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
While debugging a bpf ELF loading issue, I needed to correlate the
ELF section number with the failed relocation section reference.
Thus, add section numbers/index to the pr_debug.
In debug mode, also print section that were skipped. This helped
me identify that a section (.eh_frame) was skipped, and this was
the reason the relocation section (.rel.eh_frame) could not find
that section number.
The section numbers corresponds to the readelf tools Section Headers [Nr].
Signed-off-by: Jesper Dangaard Brouer <redacted>
---
tools/lib/bpf/libbpf.c | 25 +++++++++++++------------
1 file changed, 13 insertions(+), 12 deletions(-)
@@ -315,8 +315,8 @@ bpf_program__init(void *data, size_t size, char *section_name, int idx,prog->section_name=strdup(section_name);if(!prog->section_name){-pr_warning("failed to alloc name for prog under section %s\n",-section_name);+pr_warning("failed to alloc name for prog under section(%d) %s\n",+idx,section_name);gotoerrout;}
@@ -759,29 +759,29 @@ static int bpf_object__elf_collect(struct bpf_object *obj)idx++;if(gelf_getshdr(scn,&sh)!=&sh){-pr_warning("failed to get section header from %s\n",-obj->path);+pr_warning("failed to get section(%d) header from %s\n",+idx,obj->path);err=-LIBBPF_ERRNO__FORMAT;gotoout;}name=elf_strptr(elf,ep->e_shstrndx,sh.sh_name);if(!name){-pr_warning("failed to get section name from %s\n",-obj->path);+pr_warning("failed to get section(%d) name from %s\n",+idx,obj->path);err=-LIBBPF_ERRNO__FORMAT;gotoout;}data=elf_getdata(scn,0);if(!data){-pr_warning("failed to get section data from %s(%s)\n",-name,obj->path);+pr_warning("failed to get section(%d) data from %s(%s)\n",+idx,name,obj->path);err=-LIBBPF_ERRNO__FORMAT;gotoout;}-pr_debug("section %s, size %ld, link %d, flags %lx, type=%d\n",-name,(unsignedlong)data->d_size,+pr_debug("section(%d) %s, size %ld, link %d, flags %lx, type=%d\n",+idx,name,(unsignedlong)data->d_size,(int)sh.sh_link,(unsignedlong)sh.sh_flags,(int)sh.sh_type);
V2: Moved program into selftests/bpf from tools/libbpf
This program can be used on its own for testing/debugging if a
BPF ELF-object file can be loaded with libbpf (from tools/lib/bpf).
If something is wrong with the ELF object, the program have
a --debug mode that will display the ELF sections and especially
the skipped sections. This allows for quickly identifying the
problematic ELF section number, which can be corrolated with the
readelf tool.
The program signal error via return codes, and also have
a --quiet mode, which is practical for use in scripts like
selftests/bpf.
Signed-off-by: Jesper Dangaard Brouer <redacted>
---
tools/testing/selftests/bpf/Makefile | 2
tools/testing/selftests/bpf/test_libbpf_open.c | 150 ++++++++++++++++++++++++
2 files changed, 151 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/bpf/test_libbpf_open.c
This script test_libbpf.sh will be part of the 'make run_tests'
invocation, but can also be invoked manually in this directory,
and a verbose mode can be enabled via setting the environment
variable $VERBOSE like:
$ VERBOSE=yes ./test_libbpf.sh
The script contains some tests that are commented out, as they
currently fail. They are reminders about what we need to improve
for the libbpf loader library.
Signed-off-by: Jesper Dangaard Brouer <redacted>
---
tools/testing/selftests/bpf/Makefile | 14 +++++++-
tools/testing/selftests/bpf/test_libbpf.sh | 49 ++++++++++++++++++++++++++++
2 files changed, 61 insertions(+), 2 deletions(-)
create mode 100755 tools/testing/selftests/bpf/test_libbpf.sh
@@ -13,6 +13,7 @@ endifCFLAGS+=-Wall-O2-I$(APIDIR)-I$(LIBDIR)-I$(GENDIR)$(GENFLAGS)-I../../../includeLDLIBS+=-lcap-lelf-lrt-lpthread+# Order correspond to 'make run_tests' orderTEST_GEN_PROGS=test_verifiertest_tagtest_mapstest_lru_maptest_lpm_maptest_progs\test_aligntest_verifier_logtest_dev_cgrouptest_tcpbpf_user
@@ -20,17 +21,26 @@ TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o testtest_pkt_md_access.otest_xdp_redirect.otest_xdp_meta.osockmap_parse_prog.o\sockmap_verdict_prog.odev_cgroup.osample_ret0.otest_tracepoint.o\test_l4lb_noinline.otest_xdp_noinline.otest_stacktrace_map.o\-sample_map_ret0.otest_tcpbpf_kern.otest_libbpf_open+sample_map_ret0.otest_tcpbpf_kern.o-TEST_PROGS:=test_kmod.shtest_xdp_redirect.shtest_xdp_meta.sh\+# Order correspond to 'make run_tests' order+TEST_PROGS:=test_kmod.sh\+test_libbpf.sh\+test_xdp_redirect.sh\+test_xdp_meta.sh\test_offload.py+# Compile but not part of 'make run_tests'+TEST_GEN_PROGS_EXTENDED=test_libbpf_open+include ../lib.mkBPFOBJ:=$(OUTPUT)/libbpf.a$(OUTPUT)/cgroup_helpers.c$(TEST_GEN_PROGS):$(BPFOBJ)+$(TEST_GEN_PROGS_EXTENDED):$(OUTPUT)/libbpf.a+.PHONY:force# force a rebuild of BPFOBJ when its dependencies are updated
If clang >= 4.0.1 is missing the option '-target bpf', it will cause
llc/llvm to create two ELF sections for "Exception Frames", with
section names '.eh_frame' and '.rel.eh_frame'.
The BPF ELF loader library libbpf fails when loading files with these
sections. The other in-kernel BPF ELF loader in samples/bpf/bpf_load.c,
handle this gracefully. And iproute2 loader also seems to work with these
"eh" sections.
The issue in libbpf is caused by bpf_object__elf_collect() skip the
'.eh_frame' and thus doesn't create an internal data structure
pointing to this ELF section index. Later when the relocation section
'.rel.eh_frame' is processed, it tries to find the '.eh_frame' via the
ELF section idx, which is that fails (in bpf_object__collect_reloc).
I couldn't find a way to see that the '.rel.eh_frame' was irrelevant
(that is only determined by looking at the section it reference, which
we no longer have info available on).
Thus, my solution is simply to match on the name of the relocation
section, to skip that too.
Note, for samples/bpf/ the '-target bpf' parameter to clang cannot be used
due to incompatibility with asm embedded headers, that some of the samples
include. This is explained in more details by Yonghong Song in bpf_devel_QA.
Signed-off-by: Jesper Dangaard Brouer <redacted>
---
tools/lib/bpf/libbpf.c | 7 +++++++
1 file changed, 7 insertions(+)
On Tue, Feb 06, 2018 at 03:54:28PM +0100, Jesper Dangaard Brouer wrote:
If clang >= 4.0.1 is missing the option '-target bpf', it will cause
llc/llvm to create two ELF sections for "Exception Frames", with
section names '.eh_frame' and '.rel.eh_frame'.
The BPF ELF loader library libbpf fails when loading files with these
sections. The other in-kernel BPF ELF loader in samples/bpf/bpf_load.c,
handle this gracefully. And iproute2 loader also seems to work with these
"eh" sections.
The issue in libbpf is caused by bpf_object__elf_collect() skip the
'.eh_frame' and thus doesn't create an internal data structure
pointing to this ELF section index. Later when the relocation section
'.rel.eh_frame' is processed, it tries to find the '.eh_frame' via the
ELF section idx, which is that fails (in bpf_object__collect_reloc).
I couldn't find a way to see that the '.rel.eh_frame' was irrelevant
(that is only determined by looking at the section it reference, which
we no longer have info available on).
but does this approach work for all extra sections and relocations emitted
when source is compiled with -g ?
To address this case bpf_load.c does:
if (shdr.sh_type == SHT_REL) {
struct bpf_insn *insns;
/* locate prog sec that need map fixup (relocations) */
if (get_sec(elf, shdr.sh_info, &ehdr, &shname_prog,
&shdr_prog, &data_prog))
continue;
if (shdr_prog.sh_type != SHT_PROGBITS ||
!(shdr_prog.sh_flags & SHF_EXECINSTR))
continue;
why the same approach is not applicable here?
I guess we can apply this workaround as-is but it looks incomplete.
quoted hunk
Thus, my solution is simply to match on the name of the relocation
section, to skip that too.
Note, for samples/bpf/ the '-target bpf' parameter to clang cannot be used
due to incompatibility with asm embedded headers, that some of the samples
include. This is explained in more details by Yonghong Song in bpf_devel_QA.
Signed-off-by: Jesper Dangaard Brouer <redacted>
---
tools/lib/bpf/libbpf.c | 7 +++++++
1 file changed, 7 insertions(+)
On Tue, 6 Feb 2018 08:00:59 -0800 Alexei Starovoitov [off-list ref] wrote:
On Tue, Feb 06, 2018 at 03:54:28PM +0100, Jesper Dangaard Brouer wrote:
quoted
If clang >= 4.0.1 is missing the option '-target bpf', it will cause
llc/llvm to create two ELF sections for "Exception Frames", with
section names '.eh_frame' and '.rel.eh_frame'.
The BPF ELF loader library libbpf fails when loading files with these
sections. The other in-kernel BPF ELF loader in samples/bpf/bpf_load.c,
handle this gracefully. And iproute2 loader also seems to work with these
"eh" sections.
The issue in libbpf is caused by bpf_object__elf_collect() skip the
'.eh_frame' and thus doesn't create an internal data structure
pointing to this ELF section index. Later when the relocation section
'.rel.eh_frame' is processed, it tries to find the '.eh_frame' via the
ELF section idx, which is that fails (in bpf_object__collect_reloc).
I couldn't find a way to see that the '.rel.eh_frame' was irrelevant
(that is only determined by looking at the section it reference, which
we no longer have info available on).
but does this approach work for all extra sections and relocations emitted
when source is compiled with -g ?
No, but I plan to follow up and do a more complete solution later. This
is a workaround to get the Suricata use-case working and also that
samples/bpf/ can be loaded.
To address this case bpf_load.c does:
if (shdr.sh_type == SHT_REL) {
struct bpf_insn *insns;
/* locate prog sec that need map fixup (relocations) */
if (get_sec(elf, shdr.sh_info, &ehdr, &shname_prog,
&shdr_prog, &data_prog))
continue;
if (shdr_prog.sh_type != SHT_PROGBITS ||
!(shdr_prog.sh_flags & SHF_EXECINSTR))
continue;
why the same approach is not applicable here?
As described above bpf_object__elf_collect() skip the "real" section
that the relo-section want to lookup (based on the same kind of
check), but libbpf is now missing the section idx in its internal
structures... and thus the relo lookup of the idx fails. (bpf_load.c
does the lookup in the ELF obj directly, thus it does not have this
problem).
I guess we can apply this workaround as-is but it looks incomplete.
Yes, it is a workaround to move forward... it requires a larger change
to libbpf, so it stores idx'es of skipped sections.
--
Best regards,
Jesper Dangaard Brouer
MSc.CS, Principal Kernel Engineer at Red Hat
LinkedIn: http://www.linkedin.com/in/brouer
From: Daniel Borkmann <daniel@iogearbox.net> Date: 2018-02-06 19:05:48
On 02/06/2018 06:03 PM, Jesper Dangaard Brouer wrote:
On Tue, 6 Feb 2018 08:00:59 -0800 Alexei Starovoitov [off-list ref] wrote:
quoted
On Tue, Feb 06, 2018 at 03:54:28PM +0100, Jesper Dangaard Brouer wrote:
quoted
If clang >= 4.0.1 is missing the option '-target bpf', it will cause
llc/llvm to create two ELF sections for "Exception Frames", with
section names '.eh_frame' and '.rel.eh_frame'.
The BPF ELF loader library libbpf fails when loading files with these
sections. The other in-kernel BPF ELF loader in samples/bpf/bpf_load.c,
handle this gracefully. And iproute2 loader also seems to work with these
"eh" sections.
The issue in libbpf is caused by bpf_object__elf_collect() skip the
'.eh_frame' and thus doesn't create an internal data structure
pointing to this ELF section index. Later when the relocation section
'.rel.eh_frame' is processed, it tries to find the '.eh_frame' via the
ELF section idx, which is that fails (in bpf_object__collect_reloc).
I couldn't find a way to see that the '.rel.eh_frame' was irrelevant
(that is only determined by looking at the section it reference, which
we no longer have info available on).
but does this approach work for all extra sections and relocations emitted
when source is compiled with -g ?
No, but I plan to follow up and do a more complete solution later. This
is a workaround to get the Suricata use-case working and also that
samples/bpf/ can be loaded.
Aside from a needed fix in any case, is there a specifc reason why Suricata
cannot rely on 'clang -target bpf'? Is it asm inline headers in your case?
quoted
To address this case bpf_load.c does:
if (shdr.sh_type == SHT_REL) {
struct bpf_insn *insns;
/* locate prog sec that need map fixup (relocations) */
if (get_sec(elf, shdr.sh_info, &ehdr, &shname_prog,
&shdr_prog, &data_prog))
continue;
if (shdr_prog.sh_type != SHT_PROGBITS ||
!(shdr_prog.sh_flags & SHF_EXECINSTR))
continue;
why the same approach is not applicable here?
As described above bpf_object__elf_collect() skip the "real" section
that the relo-section want to lookup (based on the same kind of
check), but libbpf is now missing the section idx in its internal
structures... and thus the relo lookup of the idx fails. (bpf_load.c
does the lookup in the ELF obj directly, thus it does not have this
problem).
Out of curiosity, I just double checked iproute2 loader (examples/bpf/):
$ clang -O2 -g -emit-llvm -c bpf_cyclic.c -o - | llc -march=bpf -mcpu=probe -filetype=obj -o bpf_cyclic.o
$ readelf -a bpf_cyclic.o | grep "\["
[Nr] Name Type Address Offset
[ 0] NULL 0000000000000000 00000000
[ 1] .strtab STRTAB 0000000000000000 000016b0
[ 2] .text PROGBITS 0000000000000000 00000040
[ 3] 0xabccba/0 PROGBITS 0000000000000000 00000040
[ 4] .rel0xabccba/0 REL 0000000000000000 00001120
[ 5] classifier PROGBITS 0000000000000000 000000e8
[ 6] .relclassifier REL 0000000000000000 00001130
[ 7] maps PROGBITS 0000000000000000 00000118
[ 8] license PROGBITS 0000000000000000 0000013c
[ 9] .debug_str PROGBITS 0000000000000000 00000140
[10] .debug_loc PROGBITS 0000000000000000 000003d5
[11] .rel.debug_loc REL 0000000000000000 00001140
[12] .debug_abbrev PROGBITS 0000000000000000 0000045a
[13] .debug_info PROGBITS 0000000000000000 0000055c
[14] .rel.debug_info REL 0000000000000000 000011c0
[15] .debug_ranges PROGBITS 0000000000000000 0000088c
[16] .rel.debug_ranges REL 0000000000000000 000015d0
[17] .debug_macinfo PROGBITS 0000000000000000 000008ec
[18] .debug_pubnames PROGBITS 0000000000000000 000008ed
[19] .rel.debug_pubnam REL 0000000000000000 00001650
[20] .debug_pubtypes PROGBITS 0000000000000000 00000954
[21] .rel.debug_pubtyp REL 0000000000000000 00001660
[22] .eh_frame PROGBITS 0000000000000000 000009c0
[23] .rel.eh_frame REL 0000000000000000 00001670
[24] .debug_line PROGBITS 0000000000000000 00000a10
[25] .rel.debug_line REL 0000000000000000 00001690
[26] .symtab SYMTAB 0000000000000000 00000b08
# tc qdisc add dev lo clsact
# tc filter add dev lo ingress bpf da obj bpf_cyclic.o
# tc filter show dev lo ingress
filter protocol all pref 49152 bpf chain 0
filter protocol all pref 49152 bpf chain 0 handle 0x1 bpf_cyclic.o:[classifier] direct-action not_in_hw id 6 tag 736a8a004dead229
So no problems. What it does internally is pretty similar to what Alexei
described; for programs, they need to have ELF section header type of
SHT_PROGBITS and section header flags must match on SHF_EXECINSTR in
the relocation parsing.
Now, picking out two, and looking at the flags:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[...]
[ 5] classifier PROGBITS 0000000000000000 000000e8
0000000000000030 0000000000000000 AX 0 0 8
[...]
[22] .eh_frame PROGBITS 0000000000000000 000009c0
0000000000000050 0000000000000000 A 0 0 8
[...]
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
So .eh_frame doesn't even have SHF_EXECINSTR set. Why it cannot test on
this? Doing strcmp(name, ".rel.eh_frame") == 0 test is indeed a bit
fragile in the sense that we would also need to strcmp() all the others
listed above since libbpf could trip over them just as well. When you
check the SHT_REL sections, the target section index sits in GElf_Shdr's
.sh_info, so the only thing that would need to be done in this case is
to look up the ELF section header with index from .sh_info, get the
GElf_Shdr section header and check for a match on SHT_PROGBITS/SHF_EXECINSTR,
otherwise skip that SHT_REL section. A direct lookup of the index in
the obj would not require any complex section/index tracking or larger
rework in libbpf, hmm, what am I missing?
quoted
I guess we can apply this workaround as-is but it looks incomplete.
Yes, it is a workaround to move forward... it requires a larger change
to libbpf, so it stores idx'es of skipped sections.
On Tue, 6 Feb 2018 20:05:43 +0100 Daniel Borkmann [off-list ref] wrote:
On 02/06/2018 06:03 PM, Jesper Dangaard Brouer wrote:
[...]
quoted
[...] I plan to follow up and do a more complete solution later. This
is a workaround to get the Suricata use-case working and also that
samples/bpf/ can be loaded.
Aside from a needed fix in any case, is there a specifc reason why Suricata
cannot rely on 'clang -target bpf'? Is it asm inline headers in your case?
Below is the error I get when using 'clang' with '-target bpf'
$ dirs
~/git/suricata/src/ebpf
$ clang -Wall -Iinclude -O2 -D__KERNEL__ -target bpf -emit-llvm -c xdp_filter.c -o - | llc -march=bpf -filetype=obj -o xdp_filter.bpf
In file included from xdp_filter.c:19:
In file included from /usr/bin/../lib64/clang/4.0.1/include/stdint.h:63:
In file included from /usr/include/stdint.h:26:
In file included from /usr/include/bits/libc-header-start.h:33:
In file included from /usr/include/features.h:434:
/usr/include/gnu/stubs.h:7:11: fatal error: 'gnu/stubs-32.h' file not found
# include <gnu/stubs-32.h>
^~~~~~~~~~~~~~~~
I'll leave it up to Eric Leblond to figure out that he need to change
in the eBPF programs to make it compile with '-target bpf'. Maybe you
can offer him some guidance here?
Direct link to code:
https://github.com/OISF/suricata/blob/master/ebpf/xdp_filter.c
--
Best regards,
Jesper Dangaard Brouer
MSc.CS, Principal Kernel Engineer at Red Hat
LinkedIn: http://www.linkedin.com/in/brouer
From: Daniel Borkmann <daniel@iogearbox.net> Date: 2018-02-07 13:19:04
On 02/07/2018 01:40 PM, Jesper Dangaard Brouer wrote:
On Tue, 6 Feb 2018 20:05:43 +0100 Daniel Borkmann [off-list ref] wrote:
quoted
On 02/06/2018 06:03 PM, Jesper Dangaard Brouer wrote:
[...]
quoted
quoted
[...] I plan to follow up and do a more complete solution later. This
is a workaround to get the Suricata use-case working and also that
samples/bpf/ can be loaded.
Aside from a needed fix in any case, is there a specifc reason why Suricata
cannot rely on 'clang -target bpf'? Is it asm inline headers in your case?
Below is the error I get when using 'clang' with '-target bpf'
$ dirs
~/git/suricata/src/ebpf
$ clang -Wall -Iinclude -O2 -D__KERNEL__ -target bpf -emit-llvm -c xdp_filter.c -o - | llc -march=bpf -filetype=obj -o xdp_filter.bpf
In file included from xdp_filter.c:19:
In file included from /usr/bin/../lib64/clang/4.0.1/include/stdint.h:63:
In file included from /usr/include/stdint.h:26:
In file included from /usr/include/bits/libc-header-start.h:33:
In file included from /usr/include/features.h:434:
/usr/include/gnu/stubs.h:7:11: fatal error: 'gnu/stubs-32.h' file not found
# include <gnu/stubs-32.h>
^~~~~~~~~~~~~~~~
I'll leave it up to Eric Leblond to figure out that he need to change
in the eBPF programs to make it compile with '-target bpf'. Maybe you
can offer him some guidance here?
Direct link to code:
https://github.com/OISF/suricata/blob/master/ebpf/xdp_filter.c
Sure, you just need glibc-devel.i686, see:
$ clang -Wall -Iinclude -O2 -D__KERNEL__ -target bpf -emit-llvm -c xdp_filter.c -o - | llc -march=bpf -filetype=obj -o xdp_filter.bpf
In file included from xdp_filter.c:19:
In file included from /home/darkstar/llvm/build/lib/clang/7.0.0/include/stdint.h:63:
In file included from /usr/include/stdint.h:25:
In file included from /usr/include/features.h:392:
/usr/include/gnu/stubs.h:7:11: fatal error: 'gnu/stubs-32.h' file not found
# include <gnu/stubs-32.h>
^~~~~~~~~~~~~~~~
1 error generated.
# yum install glibc-devel.i686
[...]
$ clang -Wall -Iinclude -O2 -D__KERNEL__ -target bpf -emit-llvm -c xdp_filter.c -o - | llc -march=bpf -filetype=obj -o xdp_filter.bpf
$
Alternatively, you could do something like done in selftests to provide a
dummy, see commit 1c2dd16add7e ("selftests/bpf: get rid of -D__x86_64__").
Cheers,
Daniel
On Wed, 7 Feb 2018 14:19:00 +0100
Daniel Borkmann [off-list ref] wrote:
On 02/07/2018 01:40 PM, Jesper Dangaard Brouer wrote:
quoted
On Tue, 6 Feb 2018 20:05:43 +0100 Daniel Borkmann [off-list ref] wrote:
quoted
On 02/06/2018 06:03 PM, Jesper Dangaard Brouer wrote:
[...]
quoted
quoted
[...] I plan to follow up and do a more complete solution later. This
is a workaround to get the Suricata use-case working and also that
samples/bpf/ can be loaded.
Aside from a needed fix in any case, is there a specifc reason why Suricata
cannot rely on 'clang -target bpf'? Is it asm inline headers in your case?
Below is the error I get when using 'clang' with '-target bpf'
$ dirs
~/git/suricata/src/ebpf
$ clang -Wall -Iinclude -O2 -D__KERNEL__ -target bpf -emit-llvm -c xdp_filter.c -o - | llc -march=bpf -filetype=obj -o xdp_filter.bpf
In file included from xdp_filter.c:19:
In file included from /usr/bin/../lib64/clang/4.0.1/include/stdint.h:63:
In file included from /usr/include/stdint.h:26:
In file included from /usr/include/bits/libc-header-start.h:33:
In file included from /usr/include/features.h:434:
/usr/include/gnu/stubs.h:7:11: fatal error: 'gnu/stubs-32.h' file not found
# include <gnu/stubs-32.h>
^~~~~~~~~~~~~~~~
I'll leave it up to Eric Leblond to figure out that he need to change
in the eBPF programs to make it compile with '-target bpf'. Maybe you
can offer him some guidance here?
Direct link to code:
https://github.com/OISF/suricata/blob/master/ebpf/xdp_filter.c
Sure, you just need glibc-devel.i686, see:
$ clang -Wall -Iinclude -O2 -D__KERNEL__ -target bpf -emit-llvm -c xdp_filter.c -o - | llc -march=bpf -filetype=obj -o xdp_filter.bpf
In file included from xdp_filter.c:19:
In file included from /home/darkstar/llvm/build/lib/clang/7.0.0/include/stdint.h:63:
In file included from /usr/include/stdint.h:25:
In file included from /usr/include/features.h:392:
/usr/include/gnu/stubs.h:7:11: fatal error: 'gnu/stubs-32.h' file not found
# include <gnu/stubs-32.h>
^~~~~~~~~~~~~~~~
1 error generated.
# yum install glibc-devel.i686
[...]
$ clang -Wall -Iinclude -O2 -D__KERNEL__ -target bpf -emit-llvm -c xdp_filter.c -o - | llc -march=bpf -filetype=obj -o xdp_filter.bpf
$
Could you please explain why if makes a difference to install glibc-devel.i686 ?
How will people compiling suricata figure out the new dependency, that
on their 64-bit (x86_64) distro's they also need to install the 32-bit
(i686) variant of glibc-devel ?
Alternatively, you could do something like done in selftests to provide a
dummy, see commit 1c2dd16add7e ("selftests/bpf: get rid of -D__x86_64__").
That is a funny way to workaround the problem (having an empty
<gnu/stubs.h> file in include path), but it might be a better solution
to avoid frustrations for people compiling suricata.
An alternative solution is to NOT:
#include <stdint.h>
#include <string.h>
And then change:
uint64_t -> __u64
uint32_t -> __u32
uint16_t -> __u16
uint8_t -> __u8
--
Best regards,
Jesper Dangaard Brouer
MSc.CS, Principal Kernel Engineer at Red Hat
LinkedIn: http://www.linkedin.com/in/brouer
From: Daniel Borkmann <daniel@iogearbox.net> Date: 2018-02-07 16:18:41
On 02/07/2018 03:58 PM, Jesper Dangaard Brouer wrote:
On Wed, 7 Feb 2018 14:19:00 +0100
Daniel Borkmann [off-list ref] wrote:
quoted
On 02/07/2018 01:40 PM, Jesper Dangaard Brouer wrote:
quoted
On Tue, 6 Feb 2018 20:05:43 +0100 Daniel Borkmann [off-list ref] wrote:
quoted
On 02/06/2018 06:03 PM, Jesper Dangaard Brouer wrote:
[...]
quoted
quoted
[...] I plan to follow up and do a more complete solution later. This
is a workaround to get the Suricata use-case working and also that
samples/bpf/ can be loaded.
Aside from a needed fix in any case, is there a specifc reason why Suricata
cannot rely on 'clang -target bpf'? Is it asm inline headers in your case?
Below is the error I get when using 'clang' with '-target bpf'
$ dirs
~/git/suricata/src/ebpf
$ clang -Wall -Iinclude -O2 -D__KERNEL__ -target bpf -emit-llvm -c xdp_filter.c -o - | llc -march=bpf -filetype=obj -o xdp_filter.bpf
In file included from xdp_filter.c:19:
In file included from /usr/bin/../lib64/clang/4.0.1/include/stdint.h:63:
In file included from /usr/include/stdint.h:26:
In file included from /usr/include/bits/libc-header-start.h:33:
In file included from /usr/include/features.h:434:
/usr/include/gnu/stubs.h:7:11: fatal error: 'gnu/stubs-32.h' file not found
# include <gnu/stubs-32.h>
^~~~~~~~~~~~~~~~
I'll leave it up to Eric Leblond to figure out that he need to change
in the eBPF programs to make it compile with '-target bpf'. Maybe you
can offer him some guidance here?
Direct link to code:
https://github.com/OISF/suricata/blob/master/ebpf/xdp_filter.c
Sure, you just need glibc-devel.i686, see:
$ clang -Wall -Iinclude -O2 -D__KERNEL__ -target bpf -emit-llvm -c xdp_filter.c -o - | llc -march=bpf -filetype=obj -o xdp_filter.bpf
In file included from xdp_filter.c:19:
In file included from /home/darkstar/llvm/build/lib/clang/7.0.0/include/stdint.h:63:
In file included from /usr/include/stdint.h:25:
In file included from /usr/include/features.h:392:
/usr/include/gnu/stubs.h:7:11: fatal error: 'gnu/stubs-32.h' file not found
# include <gnu/stubs-32.h>
^~~~~~~~~~~~~~~~
1 error generated.
# yum install glibc-devel.i686
[...]
$ clang -Wall -Iinclude -O2 -D__KERNEL__ -target bpf -emit-llvm -c xdp_filter.c -o - | llc -march=bpf -filetype=obj -o xdp_filter.bpf
quoted
$
Could you please explain why if makes a difference to install glibc-devel.i686 ?
Well, see what /usr/include/gnu/stubs.h is doing, on x86_64 it's:
#if !defined __x86_64__
# include <gnu/stubs-32.h>
#endif
#if defined __x86_64__ && defined __LP64__
# include <gnu/stubs-64.h>
#endif
#if defined __x86_64__ && defined __ILP32__
# include <gnu/stubs-x32.h>
#endif
If you do clang -target bpf, then clang will have '__bpf__' defined instead
of '__x86_64__' hence the gnu/stubs-32.h include attempt, and the workaround
used in selftests with -D__x86_64__. But the -D__x86_64__ is not portable, so
yeah, either dummy stubs if you need to include the headers or also other the
workaround you mention below.
[...]
quoted
Alternatively, you could do something like done in selftests to provide a
dummy, see commit 1c2dd16add7e ("selftests/bpf: get rid of -D__x86_64__").
That is a funny way to workaround the problem (having an empty
<gnu/stubs.h> file in include path), but it might be a better solution
to avoid frustrations for people compiling suricata.
An alternative solution is to NOT:
#include <stdint.h>
#include <string.h>
And then change:
uint64_t -> __u64
uint32_t -> __u32
uint16_t -> __u16
uint8_t -> __u8
Hi Eric,
I've improved the Suricata ebpf makefile, in-order to avoid generating
the .eh_frame sections. This required changing the code a bit, to
allow using clang -target bpf.
The makefile have also been improved to stop on clang compile errors,
instead of generating an almost empty BPF ELF file.
Could I ask you to get these changes into Suricata, through correct
process for this Open Source project?
--Jesper
---
Jesper Dangaard Brouer (3):
suricata/ebpf: take clang -target bpf include issue of stdint.h into account
suricata/ebpf: compile with clang -target bpf
suricata/ebpf: improving the ebpf makefile
ebpf/Makefile.am | 22 ++++++++++++++++++----
ebpf/bypass_filter.c | 27 +++++++++++++--------------
ebpf/filter.c | 3 +--
ebpf/hash_func01.h | 12 ++++++------
ebpf/lb.c | 11 +++++------
ebpf/vlan_filter.c | 5 ++---
ebpf/xdp_filter.c | 42 ++++++++++++++++++++----------------------
7 files changed, 65 insertions(+), 57 deletions(-)
From: Jesper Dangaard Brouer <redacted>
This patch prepares code before enabling the clang -target bpf.
The clang compiler does not like #include <stdint.h> when
using '-target bpf' it will fail with:
fatal error: 'gnu/stubs-32.h' file not found
This is because using clang -target bpf, then clang will have '__bpf__'
defined instead of '__x86_64__' hence the gnu/stubs-32.h include
attempt as /usr/include/gnu/stubs.h contains, on x86_64:
#if !defined __x86_64__
# include <gnu/stubs-32.h>
#endif
#if defined __x86_64__ && defined __LP64__
# include <gnu/stubs-64.h>
#endif
#if defined __x86_64__ && defined __ILP32__
# include <gnu/stubs-x32.h>
#endif
This can be worked around by installing the 32-bit version of
glibc-devel.i686 on your distribution.
But the BPF programs does not really need to include stdint.h,
if converting:
uint64_t -> __u64
uint32_t -> __u32
uint16_t -> __u16
uint8_t -> __u8
This patch does this type syntax conversion.
Signed-off-by: Jesper Dangaard Brouer <redacted>
---
ebpf/bypass_filter.c | 27 +++++++++++++--------------
ebpf/filter.c | 3 +--
ebpf/hash_func01.h | 12 ++++++------
ebpf/lb.c | 11 +++++------
ebpf/vlan_filter.c | 5 ++---
ebpf/xdp_filter.c | 42 ++++++++++++++++++++----------------------
6 files changed, 47 insertions(+), 53 deletions(-)
@@ -24,7 +23,7 @@#define LINUX_VERSION_CODE 263682intSEC("filter")hashfilter(struct__sk_buff*skb){-uint16_tvlan_id=skb->vlan_tci&0x0fff;+__u16vlan_id=skb->vlan_tci&0x0fff;/* accept VLAN 2 and 4 and drop the rest */switch(vlan_id){case2:
@@ -38,4 +37,4 @@ int SEC("filter") hashfilter(struct __sk_buff *skb) {char__license[]SEC("license")="GPL";-uint32_t__versionSEC("version")=LINUX_VERSION_CODE;+__u32__versionSEC("version")=LINUX_VERSION_CODE;
From: Jesper Dangaard Brouer <redacted>
Enable compiling eBPF programs with clang -target bpf.
This is mostly to workaround a bug in libbpf, where clang > ver 4.0.0
generates some ELF sections (.eh_frame) when -target bpf is NOT specified,
and libbpf fails loading such files.
Notice libbpf is provided by the kernel, and in kernel v4.16 the library
will contain the needed function for attatching to the XDP hook.
Kernel commit 949abbe88436 ("libbpf: add function to setup XDP")
https://git.kernel.org/torvalds/c/949abbe88436
As it looks now, the library fix will not get into kernel v4.16. Thus, we
need this workaround for Suricata. In-order to recommend people installing
the library libbpf from kernel v4.16.
Signed-off-by: Jesper Dangaard Brouer <redacted>
---
ebpf/Makefile.am | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
From: Jesper Dangaard Brouer <redacted>
The current ebpf/Makefile.am have the problem that clang compile
errors still result in an ELF .bpf output file. This is obviously
problematic as the the problem is first seen runtime when loading
the bpf-prog. This this is cause by the uses of a pipe from
clang to llc.
To address this problem, split up the clang and llc invocations
up into two separate commands, to get proper reaction based on
the compiler exit code. The clang compiler is used as a
frontend (+ optimizer) and instructed (via -S -emit-llvm) to
generate LLVM IR (Intermediate Representation) with suffix .ll.
The LLVM llc command is used as a compiler backend taking IR and
producing BPF machine bytecode, and storing this into a ELF
object. In the last step the IR .ll suffix code it removed.
The official documentation of the IR language:
http://llvm.org/docs/LangRef.html
Also fix the previous make portability warning:
'%-style pattern rules are a GNU make extension'
I instead use some static pattern rules:
https://www.gnu.org/software/make/manual/html_node/Static-Usage.html
Signed-off-by: Jesper Dangaard Brouer <redacted>
---
ebpf/Makefile.am | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
@@ -3,11 +3,25 @@ if BUILD_EBPF# Maintaining a local copy of UAPI linux/bpf.hBPF_CFLAGS=-Iinclude-all:lb.bpffilter.bpfbypass_filter.bpfxdp_filter.bpfvlan_filter.bpf+CLANG=${CC}-%.bpf:%.c-${CC}-Wall$(BPF_CFLAGS)-O2-D__KERNEL__-D__ASM_SYSREG_H-targetbpf-emit-llvm-c$<-o-|${LLC}-march=bpf-filetype=obj-o$@+BPF_TARGETS=lb.bpf+BPF_TARGETS+=filter.bpf+BPF_TARGETS+=bypass_filter.bpf+BPF_TARGETS+=xdp_filter.bpf+BPF_TARGETS+=vlan_filter.bpf-CLEANFILES=*.bpf+all:$(BPF_TARGETS)++$(BPF_TARGETS):%.bpf: %.c+# From C-code to LLVM-IR format suffix .ll (clang -S -emit-llvm)+${CLANG}-Wall$(BPF_CFLAGS)-O2\+-D__KERNEL__-D__ASM_SYSREG_H\+-targetbpf-S-emit-llvm$<-o${@:.bpf=.ll}+# From LLVM-IR to BPF-bytecode in ELF-obj file+${LLC}-march=bpf-filetype=obj${@:.bpf=.ll}-o$@+${RM}${@:.bpf=.ll}++CLEANFILES=*.bpf*.llendif
From: Eric Leblond <hidden> Date: 2018-02-07 22:38:20
Hello Jesper,
On Wed, 2018-02-07 at 23:21 +0100, Jesper Dangaard Brouer wrote:
Hi Eric,
I've improved the Suricata ebpf makefile, in-order to avoid
generating
the .eh_frame sections. This required changing the code a bit, to
allow using clang -target bpf.
The makefile have also been improved to stop on clang compile errors,
instead of generating an almost empty BPF ELF file.
Could I ask you to get these changes into Suricata, through correct
process for this Open Source project?
Sure, I'm reviewing the code, testing it and I will do a Pull Request
on github.
Thanks a lot for that, that's a really valuable help!
BR,
--
Eric Leblond [off-list ref]
From: Eric Leblond <hidden> Date: 2018-02-07 23:52:15
Hi,
On Wed, 2018-02-07 at 23:21 +0100, Jesper Dangaard Brouer wrote:
From: Jesper Dangaard Brouer <redacted>
This patch prepares code before enabling the clang -target bpf.
The clang compiler does not like #include <stdint.h> when
using '-target bpf' it will fail with:
fatal error: 'gnu/stubs-32.h' file not found
...
This can be worked around by installing the 32-bit version of
glibc-devel.i686 on your distribution.
But the BPF programs does not really need to include stdint.h,
if converting:
uint64_t -> __u64
uint32_t -> __u32
uint16_t -> __u16
uint8_t -> __u8
This patch does this type syntax conversion.
There is an issue for system like Debian because they don't have a
asm/types.h in the include path if the architecture is not defined
which is the case due to target bpf. This results in:
clang-5.0 -Wall -Iinclude -O2 \
-D__KERNEL__ -D__ASM_SYSREG_H \
-target bpf -S -emit-llvm vlan_filter.c -o vlan_filter.ll
In file included from vlan_filter.c:19:
In file included from include/linux/bpf.h:11:
/usr/include/linux/types.h:5:10: fatal error: 'asm/types.h' file not
found
#include <asm/types.h>
^~~~~~~~~~~~~
1 error generated.
Makefile:523: recipe for target 'vlan_filter.bpf' failed
To go into details, the Debian package providing the 'asm/typs.h'
include is the the headers or linux-libc-dev. But this package comes
with a flavor and thus we have a prefix:
linux-libc-dev:amd64: /usr/include/x86_64-linux-gnu/asm/types.h
"Fun" part here is that if you build a debian package of the via make
in Linux tree then the linux-libc-dev package is correct.
So I propose the following patch that fixes the issue for me:
@@ -16,6 +16,7 @@ all: $(BPF_TARGETS)$(BPF_TARGETS):%.bpf: %.c# From C-code to LLVM-IR format suffix .ll (clang -S -emit-llvm)${CLANG}-Wall$(BPF_CFLAGS)-O2\+-I/usr/include/$(host_cpu)-$(host_os)/\-D__KERNEL__-D__ASM_SYSREG_H\-targetbpf-S-emit-llvm$<-o${@:.bpf=.ll}# From LLVM-IR to BPF-bytecode in ELF-obj file
Let me know if it is ok for you.
Best regards,
--
Eric Leblond [off-list ref]
On Thu, 08 Feb 2018 00:52:09 +0100 Eric Leblond [off-list ref] wrote:
Hi,
On Wed, 2018-02-07 at 23:21 +0100, Jesper Dangaard Brouer wrote:
quoted
From: Jesper Dangaard Brouer <redacted>
This patch prepares code before enabling the clang -target bpf.
The clang compiler does not like #include <stdint.h> when
using '-target bpf' it will fail with:
fatal error: 'gnu/stubs-32.h' file not found
...
quoted
This can be worked around by installing the 32-bit version of
glibc-devel.i686 on your distribution.
But the BPF programs does not really need to include stdint.h,
if converting:
uint64_t -> __u64
uint32_t -> __u32
uint16_t -> __u16
uint8_t -> __u8
This patch does this type syntax conversion.
There is an issue for system like Debian because they don't have a
asm/types.h in the include path if the architecture is not defined
which is the case due to target bpf. This results in:
clang-5.0 -Wall -Iinclude -O2 \
-D__KERNEL__ -D__ASM_SYSREG_H \
-target bpf -S -emit-llvm vlan_filter.c -o vlan_filter.ll
In file included from vlan_filter.c:19:
In file included from include/linux/bpf.h:11:
/usr/include/linux/types.h:5:10: fatal error: 'asm/types.h' file not
found
#include <asm/types.h>
^~~~~~~~~~~~~
1 error generated.
Makefile:523: recipe for target 'vlan_filter.bpf' failed
To go into details, the Debian package providing the 'asm/typs.h'
include is the the headers or linux-libc-dev. But this package comes
with a flavor and thus we have a prefix:
linux-libc-dev:amd64: /usr/include/x86_64-linux-gnu/asm/types.h
Oh, the joy of distro choices.
quoted hunk
"Fun" part here is that if you build a debian package of the via make
in Linux tree then the linux-libc-dev package is correct.
So I propose the following patch that fixes the issue for me:
@@ -16,6 +16,7 @@ all: $(BPF_TARGETS)$(BPF_TARGETS):%.bpf: %.c# From C-code to LLVM-IR format suffix .ll (clang -S -emit-llvm)${CLANG}-Wall$(BPF_CFLAGS)-O2\+-I/usr/include/$(host_cpu)-$(host_os)/\
Cool solution. These variables originate from configure/automake.
Would it be more technical correct to use(?): $(build_cpu)-$(build_os)
I verified that the variables are the same (notice 'make -p' trick):
$ make -p | egrep '_os'
build_os = linux-gnu
host_os = linux-gnu
$ make -p | egrep '_cpu'
host_cpu = x86_64
build_cpu = x86_64
-D__KERNEL__ -D__ASM_SYSREG_H \
-target bpf -S -emit-llvm $< -o ${@:.bpf=.ll}
# From LLVM-IR to BPF-bytecode in ELF-obj file
Let me know if it is ok for you.
I'm fine with this fix.
I wonder if we should check other distros?
--
Best regards,
Jesper Dangaard Brouer
MSc.CS, Principal Kernel Engineer at Red Hat
LinkedIn: http://www.linkedin.com/in/brouer