Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BTF kernel code support #734

Merged
merged 11 commits into from
Sep 26, 2019
Merged

BTF kernel code support #734

merged 11 commits into from
Sep 26, 2019

Conversation

olsajiri
Copy link
Contributor

@olsajiri olsajiri commented Jun 7, 2019

Adding support to use BTF kernel data to access kernel fields.

This is a follow up on recent RFC pull request addressing the
build issues and adding automated tests.

The bpftrace now detects BTF data and if present it will use it
to acess kernel related fields. With this patchset it's possible to
run following:

# bpftrace -e 'kprobe:do_dentry_open { printf("%s %d - %s %d - %s %d\n", \
                   curtask->comm, curtask->pid, \
                   curtask->parent->comm, curtask->parent->pid, \
                   curtask->parent->parent->comm, curtask->parent->parent->pid); }'
Attaching 1 probe...
chronyd 1102 - systemd 1 - swapper/0 0
bash 14311 - bash 14207 - su 14203
bash 14311 - bash 14207 - su 14203
ls 14311 - bash 14207 - su 14203
ls 14311 - bash 14207 - su 14203
systemd-journal 861 - systemd 1 - swapper/0 0
NetworkManager 1098 - systemd 1 - swapper/0 0
sssd 1099 - systemd 1 - swapper/0 0
NetworkManager 1098 - systemd 1 - swapper/0 0
NetworkManager 14312 - NetworkManager 1098 - systemd 1

The bpftrace build now detects libbpf devel package (present in Fedora 30),
and enables the btf code accordingly. It's switched off for static build now.

To use it, you still need to generate BTF kernel data manualy. I plan for
Fedora kernel to package this soon, but for now, you need to do following:

  • get latest dwarves with pahole v1.13
  • enable kernel config CONFIG_DEBUG_INFO_BTF=y and build new kernel
  • get its BTF data: $ objcopy --dump-section .BTF=./btf ./vmlinux
  • copy the btf file under /lib/modules/``uname -r``/btf
    or run bpftrace with BPFTRACE_BTF=./btf bpftrace ...

@danobi
Copy link
Member

danobi commented Jun 26, 2019

I believe @anakryiko has been working on a tool that converts BTF to a standard C header file. I wonder if we could reuse any of that.

Andrii, do you have any details you could share?

@anakryiko
Copy link

@danobi, yes, it is already available as part of bpftool. You can just do:

bpftool btf dump file <path to kernel image with .BTF section> format c

to compilable .h file.

As for the description of this change, I'm wondering why all those steps to dump .BTF section into separate file is needed? Why can't you just point to kernel image w/ .BTF and use libbpf to extract BTF data using existing API. No hassle and it should just work.

@olsajiri
Copy link
Contributor Author

olsajiri commented Jun 26, 2019

@danobi, yes, it is already available as part of bpftool. You can just do:

bpftool btf dump file <path to kernel image with .BTF section> format c

cool, will check on that

to compilable .h file.

As for the description of this change, I'm wondering why all those steps to dump .BTF section into separate file is needed? Why can't you just point to kernel image w/ .BTF and use libbpf to extract BTF data using existing API. No hassle and it should just work.

there's not always kernel-debuginfo installed on the server and we plan
to keep the BTF info in the kernel rpm itself, so it's always there

but I'll change the code to check for vmlinux if it's available

thanks,
jirka

Copy link
Member

@ajor ajor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review for files other than btf.cpp

# LIBBPF_LIBRARIES - Link these to use libbpf
# LIBBPF_DEFINITIONS - Compiler switches required for using libbpf

#if (LIBBPF_LIBRARIES AND LIBBPF_INCLUDE_DIRS)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't claim to know what these lines are for in FindLibElf.cmake, but why are they commented out here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's so people can pass in paths via cmake -D stuff. If we aren't gonna use it here we should delete it

@@ -27,6 +27,7 @@ add_executable(bpftrace_test
${CMAKE_SOURCE_DIR}/src/tracepoint_format_parser.cpp
${CMAKE_SOURCE_DIR}/src/types.cpp
${CMAKE_SOURCE_DIR}/src/utils.cpp
${CMAKE_SOURCE_DIR}/src/btf.cpp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep these in alphabetical order please

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@@ -41,6 +42,12 @@ else()
endif()
target_link_libraries(bpftrace ${LIBELF_LIBRARIES})

if (LIBBPF_FOUND AND NOT STATIC_LINKING)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no static library version of libbpf? I'm not keen on the static bpftrace having fewer features than the dynamic one

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is static version, I'll add it

* If we are using curtak in field access, retype it
* to its original type: struct task_truct.
*/
if (builtin.ident == "curtask" && builtin.is_field_access) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would people ever not want curtask defined as a struct like this? I'd say always treat it as a struct task_struct * and get rid of is_field_access

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, will check

@@ -43,6 +44,15 @@ if(HAVE_GET_CURRENT_CGROUP_ID)
endif(HAVE_GET_CURRENT_CGROUP_ID)
target_link_libraries(bpftrace_test arch ast parser resources)

find_library(BPF_LIB bpf)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this covered by find_package(LibBpf) in the base CMakeLists.txt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops.. right, I'll remove it

@danobi
Copy link
Member

danobi commented Jul 19, 2019

It looks like libbpf in net-next has support for parsing BTF ( https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/tools/bpf/bpftool/btf.c#n349 ). IMO (and also spoke w/ @anakryiko), it'd be better if we reused that code instead of writing our own parser.

It looks like your PR directly constructs bpftrace struct info but I wonder if it'd be cleaner if we had libbpf dump the type info as a C header into a string buffer and then gave LLVM/clang the string buffer.

@anakryiko
Copy link

One interesting guarantee for btf_dump__dump_type() from libbpf, which I'm not sure I emphasized anywhere, is that it will emit only requested types and all the types to ensure correct compilation, skipping everything else from BTF that's not referenced. So for bpftrace, if you already know which types are referenced from user program, then using btf_dump__dump_type() selectively will allow to produce minimal set of type definitions to satisfy build. This should improve debuggability, if anything goes wrong (less types to weed through), as well as generally speed up compilation, I'd imagine.

@olsajiri
Copy link
Contributor Author

cool, did not know about this.. will check, and repost, thanks

@olsajiri
Copy link
Contributor Author

Pushed out new version with following changes:

  • using btf_dump to get type C code and passing it through clang compile interface to get type information as suggested earlier
  • supporting so far only vmlinux BTF extraction, not to confuse things
  • add --btf option to force BTF processing, otherwise it's processed only if there are no type/includes specified

if (!btf.has_data())
return true;

// We need to process every type separately, because BTF::c_def

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm... from just briefly skimming and reading this comment, I think this is suboptimal.

It would be way more efficient to do btf_dump__new() once, then call btf_dump__dump_type() for each type you need, then compile resulting header file with all the definitions (it won't have duplicates). Creating and discarding btf_dump object for every single instance of referenced type is quite wasteful.

Also, keep in mind that btf__find_by_name() does a linear search across all types, so it might be more efficient to first create a set of type names you care about, then do single linear pass over all BTF types and dump those that match one of the name in a set. That way it's O(N) or O(NlogM) at most (if using sorted set) vs O(NM), where N is total number of BTF types, and M is number of types referenced from BPF program code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, will change

src/btf.cpp Outdated
}

btf = btf__new(data, (__u32) size);
if (IS_ERR(btf))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the way it's supposed to be done with libbpf without (re-)defining your own IS_ERR/PTR_ERR, etc is to use libbpf_get_error() function. Like this:

int err;

btf = btf__new(...);
err = libbpf_get_error(btf);
if (err) {
    // error handling
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then you can just drop MAX_ERRNO, IS_ERR, PTR_ERR, IS_ERR_VALUE definitions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, will change, thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new version pushed with review comments changes

Copy link

@anakryiko anakryiko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reviewed only btf_dump parts, haven't looked at anything else

src/btf.cpp Outdated
int err = libbpf_get_error(btf);
if (err)
{
libbpf_strerror(err, err_buf, sizeof(err_buf));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reset btf to nullptr, otherwise destructor will attempt to free invalid pointer

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, fixed

src/btf.cpp Outdated
std::set<std::string> myset(set);
__s32 id, max = (__s32) btf__get_nr_types(btf);

for (id = 1; id < max && myset.size(); id++)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id <= max

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ugh.. should have checked bpf sources.. strange, fixed

src/btf.cpp Outdated
{
const struct btf_type *t = btf__type_by_id(btf, id);

if (t == NULL)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should never happen

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, fixed

src/btf.cpp Outdated
return;
}

asprintf(&path, "/lib/modules/%s/build/vmlinux", uts.release);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, I'm working on a patch for the kernel that will expose kernel BTF through procfs or sysfs, so it will be a stable path, hopefully making this searching of kernel BTF more reliable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, also it'll be available without debuginfo package,
could you please CC me on that kernel change, thanks

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, will do

Copy link

@anakryiko anakryiko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me from libbpf/BTF standpoint. I'm not a big fan of using global set for collecting resolved types, but I'll let people more familiar with bpftrace to comment on that.

Thanks!

src/btf.cpp Outdated
return;
}

asprintf(&path, "/lib/modules/%s/build/vmlinux", uts.release);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, will do

@olsajiri
Copy link
Contributor Author

@ajor @mmarchini any comments on this one? thanks

@olsajiri
Copy link
Contributor Author

ping ;-) @ajor @mmarchini @brendangregg

thanks

@olsajiri
Copy link
Contributor Author

olsajiri commented Sep 2, 2019

FYI I'll push new version with support to check and read /sys/kernel/btf/vmlinux

Copy link
Member

@danobi danobi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some small comments but overall I really like how simple this is.

Didn't look too close at the libbpf stuff. Assuming @anakryiko took a good look at it.

src/ast/ast.cpp Outdated
@@ -5,6 +5,8 @@
namespace bpftrace {
namespace ast {

std::set<std::string> Expression::resolve = std::set<std::string>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can lead to SIOF issues if another static method / initializer uses this symbol. Instead, it's much safer to do a meyer's singleton. ie

ast.h

class Expression {
  ...
  static std::set<std::string>& getResolve();
  ...
};

ast.cpp

std::set<std::string>& Expression::getResolve() {
  static std::set<std::string> s;
  return s;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, will change

/*
* Retype curtask to its original type: struct task_truct.
*/
if (builtin.ident == "curtask") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably split this out into its own else if branch. It looks like curtask is difference enough from the other things in this branch now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@@ -30,6 +33,8 @@ class ClangParser
unsigned num_unsaved_files,
unsigned options);

bool check_diagnostics(std::string& input);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't look like input is being modified

Suggested change
bool check_diagnostics(std::string& input);
bool check_diagnostics(const std::string& input);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, will use const in here

tests/btf_data.h Outdated
@@ -0,0 +1,48 @@
#pragma once

#if 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not a comment instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

habit ;-) I'll change for /* ... */

tests/btf_data.h Outdated
0x6f, 0x32, 0x00, 0x69, 0x6e, 0x74, 0x00, 0x6c, 0x6f, 0x6e, 0x67, 0x20,
0x69, 0x6e, 0x74, 0x00
};
unsigned int btf_data_len = 268;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be better so you don't have to count the number of elements

Suggested change
unsigned int btf_data_len = 268;
unsigned int btf_data_len = sizeof(btf_data) / sizeof(btf_data[0]);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

src/btf.cpp Outdated
return vfprintf(stderr, msg, ap);
}

struct btf_location {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you put this at the top of this file so it's easier to spot?

Also, can you document what the raw field means? Not very clear from reading the struct def

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do

src/btf.cpp Outdated
struct stat st;

if (stat(file, &st))
return NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And also for the rest of this file

Suggested change
return NULL;
return nullptr;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

src/btf.cpp Outdated
const struct btf_type *t = btf__type_by_id(btf, id);
const char *str = btf_str(btf, t->name_off);

std::set<std::string>::iterator it = myset.find(str);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::set<std::string>::iterator it = myset.find(str);
auto it = myset.find(str);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

src/ast/ast.h Outdated
@@ -5,6 +5,7 @@
#include <map>
#include <string>
#include <vector>
#include <set>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer unordered_set. It's faster and I don't think we need the ordering, right?

Suggested change
#include <set>
#include <unordered_set>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, no order is needed here

{
// 'borrowed' from libbpf's bpf_core_find_kernel_btf
// from Andrii Nakryiko
struct btf_location locs_normal[] =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use std::array instead. It knows its own size so you can do a rangeed for loop over it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, will check

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on the other hand I need to know size of the std::array when
defining it, so it seems more convenient to me to use C array
in here.. however if you want to stick with c++ objects I understand
and can change it, let me know

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's kinda annoying. I think they're trying to add support for it in newer c++: https://en.cppreference.com/w/cpp/experimental/make_array . We can use that then

Copy link
Member

@danobi danobi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops marked as approved by accident

@olsajiri
Copy link
Contributor Author

thanks for review, I'll push new version

Adding default cast for curtask builtin expressions,
so we don't need to cast it explicitly like in
following script:

  # cat pid.bt
  #include <linux/sched.h>

  kprobe:do_dentry_open
  {
    printf("pid %d\n", curtask->pid);
  }

Before:
  # bpftrace pid.bt
  Can not access field 'pid' on expression of type 'integer'
  printf: %d specifier expects a value of type integer (none supplied)

After:
  # bpftrace pid.bt
  Attaching 1 probe...
  pid 4109
  ...
Adding Expression::resolve string unordered set to
keep a list of strings that need to be resolved.

It will be used in following patches as a list
to resolve with BTF data.

It's static set so it's cleared in the Driver
constructor, which is the starting point for
parsing.
It will be detected on usual place, pluys user can specify
non standard libbpf place (like /opt/libbpf/ below) with:

  $ cmake ... -DLIBBPF_INCLUDE_DIRS=/opt/libbpf/include \
              -DLIBBPF_LIBRARIES=/opt/libbpf/lib64/libbpf.so

Adding detection of btf_dump__new symbol, which is needed
in following changes. If found the LIBBPF_BTF_DUMP_FOUND
variable is set to TRUE.
Adding BTF class with constructor that checks on
available BTF data in /sys/kernel/btf/vmlinux plus
othe standard locations for vmlinux debuginfo.

The list is 'borrowed' from libbpf's bpf_core_find_kernel_btf
code from Andrii Nakryiko.

If BTF data is loaded the BTF::has_data function
returns true.

Also linking bpftrace with libbpf.
Adding c_def function to BTF class that dumps
C code definitions for given types set.
Adding ClangParser::visit_children function to contains
clang_visitChildren call, so it can be used from multiple
places.
Adding ClangParserHandler::check_diagnostics function to contain
diagnostic messages processing, so it can be used from multiple
places.
Adding SizedType::operator!= function,
to ease up next changes.
Adding ClangParser::parse_btf_definitions function
to load BTF definitions before parsing the user
specified includes. Adding also check and warning
in case those would collide.
Currently we always load BTF data before
parsing user specified includes.

Assuming that using include definitions and
BTF data are mostly mutually exclusive, this
change limits the BTF data processing to
following cases:

  - there are no includes specified by user, or
  - the --btf option is specified
Adding BTF test to clang_parser modules
with generated BTF data.
@olsajiri
Copy link
Contributor Author

@danobi could you please check on the latest version? thanks

Copy link
Member

@danobi danobi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks! This is a great addition to bpftrace

@danobi danobi merged commit 47621bb into bpftrace:master Sep 26, 2019
@olsajiri olsajiri deleted the btf branch November 11, 2019 21:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants