[bpf-next] bpf: Add drgn script to list progs/maps
diff mbox series

Message ID 20200227023253.3445221-1-rdna@fb.com
State Changes Requested
Delegated to: BPF Maintainers
Headers show
Series
  • [bpf-next] bpf: Add drgn script to list progs/maps
Related show

Commit Message

Andrey Ignatov Feb. 27, 2020, 2:32 a.m. UTC
drgn is a debugger that reads kernel memory and uses DWARF to get types
and symbols. See [1], [2] and [3] for more details on drgn.

Since drgn operates on kernel memory it has access to kernel internals
that user space doesn't. It allows to get extended info about various
kernel data structures.

Introduce bpf.py drgn script to list BPF programs and maps and their
properties unavailable to user space via kernel API.

The main use-case bpf.py covers is to show BPF programs attached to
other BPF programs via freplace/fentry/fexit mechanisms introduced
recently. There is no user-space API to get this info and e.g. bpftool
can only show all BPF programs but can't show if program A replaces a
function in program B.

Example:

  % sudo tools/bpf/bpf.py p | grep test_pkt_access
     650: BPF_PROG_TYPE_SCHED_CLS          test_pkt_access
     654: BPF_PROG_TYPE_TRACING            test_main                        linked:[650->25: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access()]
     655: BPF_PROG_TYPE_TRACING            test_subprog1                    linked:[650->29: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access_subprog1()]
     656: BPF_PROG_TYPE_TRACING            test_subprog2                    linked:[650->31: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access_subprog2()]
     657: BPF_PROG_TYPE_TRACING            test_subprog3                    linked:[650->21: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access_subprog3()]
     658: BPF_PROG_TYPE_EXT                new_get_skb_len                  linked:[650->16: BPF_TRAMP_REPLACE test_pkt_access->get_skb_len()]
     659: BPF_PROG_TYPE_EXT                new_get_skb_ifindex              linked:[650->23: BPF_TRAMP_REPLACE test_pkt_access->get_skb_ifindex()]
     660: BPF_PROG_TYPE_EXT                new_get_constant                 linked:[650->19: BPF_TRAMP_REPLACE test_pkt_access->get_constant()]

It can be seen that there is a program test_pkt_access, id 650 and there
are multiple other tracing and ext programs attached to functions in
test_pkt_access.

For example the line:

     658: BPF_PROG_TYPE_EXT                new_get_skb_len                  linked:[650->16: BPF_TRAMP_REPLACE test_pkt_access->get_skb_len()]

means that BPF program new_get_skb_len, id 658, type BPF_PROG_TYPE_EXT
replaces (BPF_TRAMP_REPLACE) function get_skb_len() that has BTF id 16
in BPF program test_pkt_access, prog id 650.

Just very simple output is supported now but it can be extended in the
future if needed.

The script is extendable and currently implements two subcommands:
* prog (alias: p) to list all BPF programs;
* map (alias: m) to list all BPF maps;

Developer can simply tweak the script to print interesting pieces of programs
or maps.

The name bpf.py is not super authentic. I'm open to better options.

The script can be sent to drgn repo where it's easier to maintain its
"drgn-ness", but in kernel tree it should be easier to maintain BPF
functionality itself what can be more important in this case.

The script depends on drgn revision [4] where BPF helpers were added.

More examples of output:

  % sudo tools/bpf/bpf.py p | shuf -n 3
      81: BPF_PROG_TYPE_CGROUP_SOCK_ADDR   tw_ipt_bind
      94: BPF_PROG_TYPE_CGROUP_SOCK_ADDR   tw_ipt_bind
      43: BPF_PROG_TYPE_KPROBE             kprobe__tcp_reno_cong_avoid

  % sudo tools/bpf/bpf.py m | shuf -n 3
     213: BPF_MAP_TYPE_HASH                errors
      30: BPF_MAP_TYPE_ARRAY               sslwall_setting
      41: BPF_MAP_TYPE_LRU_HASH            flow_to_snd

Help:

  % sudo tools/bpf/bpf.py
  usage: bpf.py [-h] {prog,p,map,m} ...

  drgn script to list BPF programs or maps and their properties
  unavailable via kernel API.

  See https://github.com/osandov/drgn/ for more details on drgn.

  optional arguments:
    -h, --help      show this help message and exit

  subcommands:
    {prog,p,map,m}
      prog (p)      list BPF programs
      map (m)       list BPF maps

[1] https://github.com/osandov/drgn/
[2] https://drgn.readthedocs.io/en/latest/index.html
[3] https://lwn.net/Articles/789641/
[4] https://github.com/osandov/drgn/commit/c8ef841768032e36581d45648e42fc2a5489d8f2

Signed-off-by: Andrey Ignatov <rdna@fb.com>
---
 tools/bpf/bpf.py | 149 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 149 insertions(+)
 create mode 100755 tools/bpf/bpf.py

Comments

Song Liu Feb. 27, 2020, 5:45 a.m. UTC | #1
> On Feb 26, 2020, at 6:32 PM, Andrey Ignatov <rdna@fb.com> wrote:
> 
> drgn is a debugger that reads kernel memory and uses DWARF to get types
> and symbols. See [1], [2] and [3] for more details on drgn.
> 
> Since drgn operates on kernel memory it has access to kernel internals
> that user space doesn't. It allows to get extended info about various
> kernel data structures.
> 
> Introduce bpf.py drgn script to list BPF programs and maps and their
> properties unavailable to user space via kernel API.
> 
> The main use-case bpf.py covers is to show BPF programs attached to
> other BPF programs via freplace/fentry/fexit mechanisms introduced
> recently. There is no user-space API to get this info and e.g. bpftool
> can only show all BPF programs but can't show if program A replaces a
> function in program B.

IIUC, bpftool misses features to show fireplace/fentry/fexit relations? 
I think we should enable that in bpftool. 

> 
> Example:
> 
>  % sudo tools/bpf/bpf.py p | grep test_pkt_access
>     650: BPF_PROG_TYPE_SCHED_CLS          test_pkt_access
>     654: BPF_PROG_TYPE_TRACING            test_main                        linked:[650->25: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access()]
>     655: BPF_PROG_TYPE_TRACING            test_subprog1                    linked:[650->29: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access_subprog1()]
>     656: BPF_PROG_TYPE_TRACING            test_subprog2                    linked:[650->31: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access_subprog2()]
>     657: BPF_PROG_TYPE_TRACING            test_subprog3                    linked:[650->21: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access_subprog3()]
>     658: BPF_PROG_TYPE_EXT                new_get_skb_len                  linked:[650->16: BPF_TRAMP_REPLACE test_pkt_access->get_skb_len()]
>     659: BPF_PROG_TYPE_EXT                new_get_skb_ifindex              linked:[650->23: BPF_TRAMP_REPLACE test_pkt_access->get_skb_ifindex()]
>     660: BPF_PROG_TYPE_EXT                new_get_constant                 linked:[650->19: BPF_TRAMP_REPLACE test_pkt_access->get_constant()]
> 
> It can be seen that there is a program test_pkt_access, id 650 and there
> are multiple other tracing and ext programs attached to functions in
> test_pkt_access.
> 
> For example the line:
> 
>     658: BPF_PROG_TYPE_EXT                new_get_skb_len                  linked:[650->16: BPF_TRAMP_REPLACE test_pkt_access->get_skb_len()]
> 
> means that BPF program new_get_skb_len, id 658, type BPF_PROG_TYPE_EXT
> replaces (BPF_TRAMP_REPLACE) function get_skb_len() that has BTF id 16
> in BPF program test_pkt_access, prog id 650.
> 
> Just very simple output is supported now but it can be extended in the
> future if needed.
> 
> The script is extendable and currently implements two subcommands:
> * prog (alias: p) to list all BPF programs;
> * map (alias: m) to list all BPF maps;
> 
> Developer can simply tweak the script to print interesting pieces of programs
> or maps.
> 
> The name bpf.py is not super authentic. I'm open to better options.

Maybe call it bpftool.py? Or bpfshow.py?

> 
> The script can be sent to drgn repo where it's easier to maintain its
> "drgn-ness", but in kernel tree it should be easier to maintain BPF
> functionality itself what can be more important in this case.
> 
> The script depends on drgn revision [4] where BPF helpers were added.
> 
> More examples of output:
> 
>  % sudo tools/bpf/bpf.py p | shuf -n 3
>      81: BPF_PROG_TYPE_CGROUP_SOCK_ADDR   tw_ipt_bind
>      94: BPF_PROG_TYPE_CGROUP_SOCK_ADDR   tw_ipt_bind
>      43: BPF_PROG_TYPE_KPROBE             kprobe__tcp_reno_cong_avoid
> 
>  % sudo tools/bpf/bpf.py m | shuf -n 3
>     213: BPF_MAP_TYPE_HASH                errors
>      30: BPF_MAP_TYPE_ARRAY               sslwall_setting
>      41: BPF_MAP_TYPE_LRU_HASH            flow_to_snd
> 
> Help:
> 
>  % sudo tools/bpf/bpf.py
>  usage: bpf.py [-h] {prog,p,map,m} ...
> 
>  drgn script to list BPF programs or maps and their properties
>  unavailable via kernel API.
> 
>  See https://github.com/osandov/drgn/ for more details on drgn.
> 
>  optional arguments:
>    -h, --help      show this help message and exit
> 
>  subcommands:
>    {prog,p,map,m}
>      prog (p)      list BPF programs
>      map (m)       list BPF maps
> 
> [1] https://github.com/osandov/drgn/
> [2] https://drgn.readthedocs.io/en/latest/index.html
> [3] https://lwn.net/Articles/789641/
> [4] https://github.com/osandov/drgn/commit/c8ef841768032e36581d45648e42fc2a5489d8f2
> 
> Signed-off-by: Andrey Ignatov <rdna@fb.com>

The code looks good to me (with name TBD). 

Acked-by: Song Liu <songliubraving@fb.com>
Andrii Nakryiko Feb. 27, 2020, 6:27 a.m. UTC | #2
On Wed, Feb 26, 2020 at 6:33 PM Andrey Ignatov <rdna@fb.com> wrote:
>
> drgn is a debugger that reads kernel memory and uses DWARF to get types
> and symbols. See [1], [2] and [3] for more details on drgn.
>
> Since drgn operates on kernel memory it has access to kernel internals
> that user space doesn't. It allows to get extended info about various
> kernel data structures.
>
> Introduce bpf.py drgn script to list BPF programs and maps and their
> properties unavailable to user space via kernel API.
>
> The main use-case bpf.py covers is to show BPF programs attached to
> other BPF programs via freplace/fentry/fexit mechanisms introduced
> recently. There is no user-space API to get this info and e.g. bpftool
> can only show all BPF programs but can't show if program A replaces a
> function in program B.
>
> Example:
>
>   % sudo tools/bpf/bpf.py p | grep test_pkt_access
>      650: BPF_PROG_TYPE_SCHED_CLS          test_pkt_access
>      654: BPF_PROG_TYPE_TRACING            test_main                        linked:[650->25: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access()]
>      655: BPF_PROG_TYPE_TRACING            test_subprog1                    linked:[650->29: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access_subprog1()]
>      656: BPF_PROG_TYPE_TRACING            test_subprog2                    linked:[650->31: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access_subprog2()]
>      657: BPF_PROG_TYPE_TRACING            test_subprog3                    linked:[650->21: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access_subprog3()]
>      658: BPF_PROG_TYPE_EXT                new_get_skb_len                  linked:[650->16: BPF_TRAMP_REPLACE test_pkt_access->get_skb_len()]
>      659: BPF_PROG_TYPE_EXT                new_get_skb_ifindex              linked:[650->23: BPF_TRAMP_REPLACE test_pkt_access->get_skb_ifindex()]
>      660: BPF_PROG_TYPE_EXT                new_get_constant                 linked:[650->19: BPF_TRAMP_REPLACE test_pkt_access->get_constant()]
>
> It can be seen that there is a program test_pkt_access, id 650 and there
> are multiple other tracing and ext programs attached to functions in
> test_pkt_access.
>
> For example the line:
>
>      658: BPF_PROG_TYPE_EXT                new_get_skb_len                  linked:[650->16: BPF_TRAMP_REPLACE test_pkt_access->get_skb_len()]
>
> means that BPF program new_get_skb_len, id 658, type BPF_PROG_TYPE_EXT
> replaces (BPF_TRAMP_REPLACE) function get_skb_len() that has BTF id 16
> in BPF program test_pkt_access, prog id 650.
>
> Just very simple output is supported now but it can be extended in the
> future if needed.
>
> The script is extendable and currently implements two subcommands:
> * prog (alias: p) to list all BPF programs;
> * map (alias: m) to list all BPF maps;
>
> Developer can simply tweak the script to print interesting pieces of programs
> or maps.
>
> The name bpf.py is not super authentic. I'm open to better options.

Just to throw another name into consideration: bpf_inspect.py?

>
> The script can be sent to drgn repo where it's easier to maintain its
> "drgn-ness", but in kernel tree it should be easier to maintain BPF
> functionality itself what can be more important in this case.

Unless it's regularly exercised as part of selftests, it will still break, IMO.


>
> The script depends on drgn revision [4] where BPF helpers were added.
>
> More examples of output:
>
>   % sudo tools/bpf/bpf.py p | shuf -n 3
>       81: BPF_PROG_TYPE_CGROUP_SOCK_ADDR   tw_ipt_bind
>       94: BPF_PROG_TYPE_CGROUP_SOCK_ADDR   tw_ipt_bind
>       43: BPF_PROG_TYPE_KPROBE             kprobe__tcp_reno_cong_avoid
>
>   % sudo tools/bpf/bpf.py m | shuf -n 3
>      213: BPF_MAP_TYPE_HASH                errors
>       30: BPF_MAP_TYPE_ARRAY               sslwall_setting
>       41: BPF_MAP_TYPE_LRU_HASH            flow_to_snd
>
> Help:
>
>   % sudo tools/bpf/bpf.py
>   usage: bpf.py [-h] {prog,p,map,m} ...
>
>   drgn script to list BPF programs or maps and their properties
>   unavailable via kernel API.
>
>   See https://github.com/osandov/drgn/ for more details on drgn.
>
>   optional arguments:
>     -h, --help      show this help message and exit
>
>   subcommands:
>     {prog,p,map,m}
>       prog (p)      list BPF programs
>       map (m)       list BPF maps
>
> [1] https://github.com/osandov/drgn/
> [2] https://drgn.readthedocs.io/en/latest/index.html
> [3] https://lwn.net/Articles/789641/
> [4] https://github.com/osandov/drgn/commit/c8ef841768032e36581d45648e42fc2a5489d8f2
>
> Signed-off-by: Andrey Ignatov <rdna@fb.com>
> ---
>  tools/bpf/bpf.py | 149 +++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 149 insertions(+)
>  create mode 100755 tools/bpf/bpf.py
>

[...]
Andrey Ignatov Feb. 27, 2020, 5:01 p.m. UTC | #3
Song Liu <songliubraving@fb.com> [Wed, 2020-02-26 21:45 -0800]:
> 
> 
> > On Feb 26, 2020, at 6:32 PM, Andrey Ignatov <rdna@fb.com> wrote:
> > 
> > The main use-case bpf.py covers is to show BPF programs attached to
> > other BPF programs via freplace/fentry/fexit mechanisms introduced
> > recently. There is no user-space API to get this info and e.g. bpftool
> > can only show all BPF programs but can't show if program A replaces a
> > function in program B.
> 
> IIUC, bpftool misses features to show fireplace/fentry/fexit relations? 
> I think we should enable that in bpftool. 

No, the main part in the comment is "there is no user-space API", i.e.
to my best knowledge kernel currently can't provide this info, bpftool
was just an example of one of the most well known users of BPF API.

Specifically everything-trampoline or attach_prog_fd (or corresponding
prog id) / attach_btf_id passed at loading time are not available to
user-space.

I believe there will be many things like this that can be expensive to
expose to user space via proper kernel API but are still useful to see
in user space. That's the point of this drgn script.


> > 
> > The name bpf.py is not super authentic. I'm open to better options.
> 
> Maybe call it bpftool.py? Or bpfshow.py?

bpftool.py may cause confustion with the genuine bpftool so I'd avoid
it, but bpfshow.py looks fine.
Andrey Ignatov Feb. 27, 2020, 5:38 p.m. UTC | #4
Andrii Nakryiko <andrii.nakryiko@gmail.com> [Wed, 2020-02-26 22:28 -0800]:
> On Wed, Feb 26, 2020 at 6:33 PM Andrey Ignatov <rdna@fb.com> wrote:
> >
> > drgn is a debugger that reads kernel memory and uses DWARF to get types
> > and symbols. See [1], [2] and [3] for more details on drgn.
> >
> > Since drgn operates on kernel memory it has access to kernel internals
> > that user space doesn't. It allows to get extended info about various
> > kernel data structures.
> >
> > Introduce bpf.py drgn script to list BPF programs and maps and their
> > properties unavailable to user space via kernel API.
> >
> > The main use-case bpf.py covers is to show BPF programs attached to
> > other BPF programs via freplace/fentry/fexit mechanisms introduced
> > recently. There is no user-space API to get this info and e.g. bpftool
> > can only show all BPF programs but can't show if program A replaces a
> > function in program B.
> >
> > Example:
> >
> >   % sudo tools/bpf/bpf.py p | grep test_pkt_access
> >      650: BPF_PROG_TYPE_SCHED_CLS          test_pkt_access
> >      654: BPF_PROG_TYPE_TRACING            test_main                        linked:[650->25: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access()]
> >      655: BPF_PROG_TYPE_TRACING            test_subprog1                    linked:[650->29: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access_subprog1()]
> >      656: BPF_PROG_TYPE_TRACING            test_subprog2                    linked:[650->31: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access_subprog2()]
> >      657: BPF_PROG_TYPE_TRACING            test_subprog3                    linked:[650->21: BPF_TRAMP_FEXIT test_pkt_access->test_pkt_access_subprog3()]
> >      658: BPF_PROG_TYPE_EXT                new_get_skb_len                  linked:[650->16: BPF_TRAMP_REPLACE test_pkt_access->get_skb_len()]
> >      659: BPF_PROG_TYPE_EXT                new_get_skb_ifindex              linked:[650->23: BPF_TRAMP_REPLACE test_pkt_access->get_skb_ifindex()]
> >      660: BPF_PROG_TYPE_EXT                new_get_constant                 linked:[650->19: BPF_TRAMP_REPLACE test_pkt_access->get_constant()]
> >
> > It can be seen that there is a program test_pkt_access, id 650 and there
> > are multiple other tracing and ext programs attached to functions in
> > test_pkt_access.
> >
> > For example the line:
> >
> >      658: BPF_PROG_TYPE_EXT                new_get_skb_len                  linked:[650->16: BPF_TRAMP_REPLACE test_pkt_access->get_skb_len()]
> >
> > means that BPF program new_get_skb_len, id 658, type BPF_PROG_TYPE_EXT
> > replaces (BPF_TRAMP_REPLACE) function get_skb_len() that has BTF id 16
> > in BPF program test_pkt_access, prog id 650.
> >
> > Just very simple output is supported now but it can be extended in the
> > future if needed.
> >
> > The script is extendable and currently implements two subcommands:
> > * prog (alias: p) to list all BPF programs;
> > * map (alias: m) to list all BPF maps;
> >
> > Developer can simply tweak the script to print interesting pieces of programs
> > or maps.
> >
> > The name bpf.py is not super authentic. I'm open to better options.
> 
> Just to throw another name into consideration: bpf_inspect.py?

bpf_inspect.py is good as well. That's probably better than bpfshow.py
since "inspect" better describes the goal of the script.

I'll use this name unless someone proposes a better one.


> > The script can be sent to drgn repo where it's easier to maintain its
> > "drgn-ness", but in kernel tree it should be easier to maintain BPF
> > functionality itself what can be more important in this case.
> 
> Unless it's regularly exercised as part of selftests, it will still break, IMO.

Yep, it may break especially since it relies on kernel internals. At
the same time I'm not sure it's worth spending time on selftest for the
script _now_:

* I don't know how useful the script will be for folks in general, if it
  turns out to be useful, I can follow up with a test later.

* It will bring drgn dependency into the tests that will need to be
  figured out separately (e.g. it can be optional but in this case the
  test will be skipped most of the time).

But we can come back to it in the future.

> > The script depends on drgn revision [4] where BPF helpers were added.
> >
> > More examples of output:
> >
> >   % sudo tools/bpf/bpf.py p | shuf -n 3
> >       81: BPF_PROG_TYPE_CGROUP_SOCK_ADDR   tw_ipt_bind
> >       94: BPF_PROG_TYPE_CGROUP_SOCK_ADDR   tw_ipt_bind
> >       43: BPF_PROG_TYPE_KPROBE             kprobe__tcp_reno_cong_avoid
> >
> >   % sudo tools/bpf/bpf.py m | shuf -n 3
> >      213: BPF_MAP_TYPE_HASH                errors
> >       30: BPF_MAP_TYPE_ARRAY               sslwall_setting
> >       41: BPF_MAP_TYPE_LRU_HASH            flow_to_snd
> >
> > Help:
> >
> >   % sudo tools/bpf/bpf.py
> >   usage: bpf.py [-h] {prog,p,map,m} ...
> >
> >   drgn script to list BPF programs or maps and their properties
> >   unavailable via kernel API.
> >
> >   See https://github.com/osandov/drgn/ for more details on drgn.
> >
> >   optional arguments:
> >     -h, --help      show this help message and exit
> >
> >   subcommands:
> >     {prog,p,map,m}
> >       prog (p)      list BPF programs
> >       map (m)       list BPF maps
> >
> > [1] https://github.com/osandov/drgn/
> > [2] https://urldefense.proofpoint.com/v2/url?u=https-3A__drgn.readthedocs.io_en_latest_index.html&d=DwIBaQ&c=5VD0RTtNlTh3ycd41b3MUw&r=3jAokpHyGuCuJ834j-tttQ&m=-2VO-M__tHrQ5sFnwPDqT5b28akZ_J1zEqXg9uejcL8&s=HlNywgEgwjqMVZo67TSoHZtynK-CsqLnbh7cYLH5znI&e= 
> > [3] https://urldefense.proofpoint.com/v2/url?u=https-3A__lwn.net_Articles_789641_&d=DwIBaQ&c=5VD0RTtNlTh3ycd41b3MUw&r=3jAokpHyGuCuJ834j-tttQ&m=-2VO-M__tHrQ5sFnwPDqT5b28akZ_J1zEqXg9uejcL8&s=uZSwKs3POR6r0nammsbhxvPur0RXFMa9w2cAnjq4Q_A&e= 
> > [4] https://github.com/osandov/drgn/commit/c8ef841768032e36581d45648e42fc2a5489d8f2
> >
> > Signed-off-by: Andrey Ignatov <rdna@fb.com>
> > ---
> >  tools/bpf/bpf.py | 149 +++++++++++++++++++++++++++++++++++++++++++++++
> >  1 file changed, 149 insertions(+)
> >  create mode 100755 tools/bpf/bpf.py
> >
> 
> [...]
Stanislav Fomichev Feb. 27, 2020, 6:01 p.m. UTC | #5
On 02/26, Andrey Ignatov wrote:
> drgn is a debugger that reads kernel memory and uses DWARF to get types
> and symbols. See [1], [2] and [3] for more details on drgn.
> 
> Since drgn operates on kernel memory it has access to kernel internals
> that user space doesn't. It allows to get extended info about various
> kernel data structures.
> 
> Introduce bpf.py drgn script to list BPF programs and maps and their
> properties unavailable to user space via kernel API.
Any reason this is not pushed to https://github.com/osandov/drgn/ ?
I have a bunch of networking helpers for drgn as well, but I was
thinking about contributing them to the drgn github, not the kernel.
IMO, seems like a better place to consolidate all drgn stuff.
Andrey Ignatov Feb. 27, 2020, 6:26 p.m. UTC | #6
Stanislav Fomichev <sdf@fomichev.me> [Thu, 2020-02-27 10:01 -0800]:
> On 02/26, Andrey Ignatov wrote:
> > drgn is a debugger that reads kernel memory and uses DWARF to get types
> > and symbols. See [1], [2] and [3] for more details on drgn.
> > 
> > Since drgn operates on kernel memory it has access to kernel internals
> > that user space doesn't. It allows to get extended info about various
> > kernel data structures.
> > 
> > Introduce bpf.py drgn script to list BPF programs and maps and their
> > properties unavailable to user space via kernel API.
> Any reason this is not pushed to https://github.com/osandov/drgn/ ?
> I have a bunch of networking helpers for drgn as well, but I was
> thinking about contributing them to the drgn github, not the kernel.
> IMO, seems like a better place to consolidate all drgn stuff.

I have this part in the commit message:

> > The script can be sent to drgn repo where it's easier to maintain its
> > "drgn-ness", but in kernel tree it should be easier to maintain BPF
> > functionality itself what can be more important in this case.

That's being said it's debatable which place is better and I'm still
trying to figure it out myself since, from what i see, there is no
widely adopted practice.

I've been contributing to drgn as well mostly in two forms:
* helpers [1];
* examples [2]

And so far I used examples/ dir as a place to keep small useful "tools"
(tcp_sock.py, cgroup.py, bpf.py).

But there is no place for bigger "scripts" or "tools" in drgn (yet?). On
the other hand I see two drgn scripts in kernel tree already:
* tools/cgroup/iocost_coef_gen.py
* tools/cgroup/iocost_monitor.py

So maybe it's time to discuss where to keep tools like this in the
future.

In this specifc case I'd love to see feedback from Omar and BPF
maintainers.


[1] https://github.com/osandov/drgn/tree/master/drgn/helpers/linux
[2] https://github.com/osandov/drgn/tree/master/examples/linux
Daniel Borkmann Feb. 27, 2020, 9:11 p.m. UTC | #7
[ +tj ]

On 2/27/20 7:26 PM, Andrey Ignatov wrote:
> Stanislav Fomichev <sdf@fomichev.me> [Thu, 2020-02-27 10:01 -0800]:
>> On 02/26, Andrey Ignatov wrote:
>>> drgn is a debugger that reads kernel memory and uses DWARF to get types
>>> and symbols. See [1], [2] and [3] for more details on drgn.
>>>
>>> Since drgn operates on kernel memory it has access to kernel internals
>>> that user space doesn't. It allows to get extended info about various
>>> kernel data structures.
>>>
>>> Introduce bpf.py drgn script to list BPF programs and maps and their
>>> properties unavailable to user space via kernel API.
>> Any reason this is not pushed to https://github.com/osandov/drgn/ ?
>> I have a bunch of networking helpers for drgn as well, but I was
>> thinking about contributing them to the drgn github, not the kernel.
>> IMO, seems like a better place to consolidate all drgn stuff.
> 
> I have this part in the commit message:
> 
>>> The script can be sent to drgn repo where it's easier to maintain its
>>> "drgn-ness", but in kernel tree it should be easier to maintain BPF
>>> functionality itself what can be more important in this case.
> 
> That's being said it's debatable which place is better and I'm still
> trying to figure it out myself since, from what i see, there is no
> widely adopted practice.
> 
> I've been contributing to drgn as well mostly in two forms:
> * helpers [1];
> * examples [2]
> 
> And so far I used examples/ dir as a place to keep small useful "tools"
> (tcp_sock.py, cgroup.py, bpf.py).
> 
> But there is no place for bigger "scripts" or "tools" in drgn (yet?). On
> the other hand I see two drgn scripts in kernel tree already:
> * tools/cgroup/iocost_coef_gen.py
> * tools/cgroup/iocost_monitor.py
> 
> So maybe it's time to discuss where to keep tools like this in the
> future.
> 
> In this specifc case I'd love to see feedback from Omar and BPF
> maintainers.

I can certainly see both sides given that drgn tools have been added to
tools/cgroup/ already. I presume if so, then these could live in tools/drgn/
which would also make it more clear what is needed to run as dependency
plus there should be be a proper high-level readme to document what developers
need to run in order to run them. But from looking at [1], I can also see that
those scripts would depend on new helpers being added/updated/deleted, so it
might be easier to add drgn/tools/ directory where scripts could be updated
in one go with updates to drgn helpers. Either way, I think it would be nice
to add documentation somewhere for getting people started.

> [1] https://github.com/osandov/drgn/tree/master/drgn/helpers/linux
> [2] https://github.com/osandov/drgn/tree/master/examples/linux
Daniel Borkmann Feb. 27, 2020, 9:32 p.m. UTC | #8
On 2/27/20 10:11 PM, Daniel Borkmann wrote:
> [ +tj ]
> 
> On 2/27/20 7:26 PM, Andrey Ignatov wrote:
>> Stanislav Fomichev <sdf@fomichev.me> [Thu, 2020-02-27 10:01 -0800]:
>>> On 02/26, Andrey Ignatov wrote:
>>>> drgn is a debugger that reads kernel memory and uses DWARF to get types
>>>> and symbols. See [1], [2] and [3] for more details on drgn.
>>>>
>>>> Since drgn operates on kernel memory it has access to kernel internals
>>>> that user space doesn't. It allows to get extended info about various
>>>> kernel data structures.
>>>>
>>>> Introduce bpf.py drgn script to list BPF programs and maps and their
>>>> properties unavailable to user space via kernel API.
>>> Any reason this is not pushed to https://github.com/osandov/drgn/ ?
>>> I have a bunch of networking helpers for drgn as well, but I was
>>> thinking about contributing them to the drgn github, not the kernel.
>>> IMO, seems like a better place to consolidate all drgn stuff.
>>
>> I have this part in the commit message:
>>
>>>> The script can be sent to drgn repo where it's easier to maintain its
>>>> "drgn-ness", but in kernel tree it should be easier to maintain BPF
>>>> functionality itself what can be more important in this case.
>>
>> That's being said it's debatable which place is better and I'm still
>> trying to figure it out myself since, from what i see, there is no
>> widely adopted practice.
>>
>> I've been contributing to drgn as well mostly in two forms:
>> * helpers [1];
>> * examples [2]
>>
>> And so far I used examples/ dir as a place to keep small useful "tools"
>> (tcp_sock.py, cgroup.py, bpf.py).
>>
>> But there is no place for bigger "scripts" or "tools" in drgn (yet?). On
>> the other hand I see two drgn scripts in kernel tree already:
>> * tools/cgroup/iocost_coef_gen.py
>> * tools/cgroup/iocost_monitor.py
>>
>> So maybe it's time to discuss where to keep tools like this in the
>> future.
>>
>> In this specifc case I'd love to see feedback from Omar and BPF
>> maintainers.
> 
> I can certainly see both sides given that drgn tools have been added to
> tools/cgroup/ already. I presume if so, then these could live in tools/drgn/
> which would also make it more clear what is needed to run as dependency
> plus there should be be a proper high-level readme to document what developers
> need to run in order to run them. But from looking at [1], I can also see that
> those scripts would depend on new helpers being added/updated/deleted, so it
> might be easier to add drgn/tools/ directory where scripts could be updated
> in one go with updates to drgn helpers. Either way, I think it would be nice
> to add documentation somewhere for getting people started.

One example that should definitely be avoided is 9ea37e24d4a9 ("iocost: Fix
iocost_monitor.py due to helper type mismatch") due to both living in separate
places. A third option to think about (if this is to be adapted by more subsystems)
could be to have all the kernel-specific helpers from drgn/helpers/linux under
tools/drgn/helpers/ in the kernel tree and the tools living under
tools/drgn/<subsys>/ e.g. tools/drgn/bpf/.

>> [1] https://github.com/osandov/drgn/tree/master/drgn/helpers/linux
>> [2] https://github.com/osandov/drgn/tree/master/examples/linux
Omar Sandoval Feb. 27, 2020, 10:19 p.m. UTC | #9
On 2/27/20 1:32 PM, Daniel Borkmann wrote:
> On 2/27/20 10:11 PM, Daniel Borkmann wrote:
>> [ +tj ]
>>
>> On 2/27/20 7:26 PM, Andrey Ignatov wrote:
>>> Stanislav Fomichev <sdf@fomichev.me> [Thu, 2020-02-27 10:01 -0800]:
>>>> On 02/26, Andrey Ignatov wrote:
>>>>> drgn is a debugger that reads kernel memory and uses DWARF to get types
>>>>> and symbols. See [1], [2] and [3] for more details on drgn.
>>>>>
>>>>> Since drgn operates on kernel memory it has access to kernel internals
>>>>> that user space doesn't. It allows to get extended info about various
>>>>> kernel data structures.
>>>>>
>>>>> Introduce bpf.py drgn script to list BPF programs and maps and their
>>>>> properties unavailable to user space via kernel API.
>>>> Any reason this is not pushed to https://github.com/osandov/drgn/ ?
>>>> I have a bunch of networking helpers for drgn as well, but I was
>>>> thinking about contributing them to the drgn github, not the kernel.
>>>> IMO, seems like a better place to consolidate all drgn stuff.
>>>
>>> I have this part in the commit message:
>>>
>>>>> The script can be sent to drgn repo where it's easier to maintain its
>>>>> "drgn-ness", but in kernel tree it should be easier to maintain BPF
>>>>> functionality itself what can be more important in this case.
>>>
>>> That's being said it's debatable which place is better and I'm still
>>> trying to figure it out myself since, from what i see, there is no
>>> widely adopted practice.
>>>
>>> I've been contributing to drgn as well mostly in two forms:
>>> * helpers [1];
>>> * examples [2]
>>>
>>> And so far I used examples/ dir as a place to keep small useful "tools"
>>> (tcp_sock.py, cgroup.py, bpf.py).
>>>
>>> But there is no place for bigger "scripts" or "tools" in drgn (yet?). On
>>> the other hand I see two drgn scripts in kernel tree already:
>>> * tools/cgroup/iocost_coef_gen.py
>>> * tools/cgroup/iocost_monitor.py
>>>
>>> So maybe it's time to discuss where to keep tools like this in the
>>> future.
>>>
>>> In this specifc case I'd love to see feedback from Omar and BPF
>>> maintainers.
>>
>> I can certainly see both sides given that drgn tools have been added to
>> tools/cgroup/ already. I presume if so, then these could live in tools/drgn/
>> which would also make it more clear what is needed to run as dependency
>> plus there should be be a proper high-level readme to document what developers
>> need to run in order to run them. But from looking at [1], I can also see that
>> those scripts would depend on new helpers being added/updated/deleted, so it
>> might be easier to add drgn/tools/ directory where scripts could be updated
>> in one go with updates to drgn helpers. Either way, I think it would be nice
>> to add documentation somewhere for getting people started.
> 
> One example that should definitely be avoided is 9ea37e24d4a9 ("iocost: Fix
> iocost_monitor.py due to helper type mismatch") due to both living in separate
> places. A third option to think about (if this is to be adapted by more subsystems)
> could be to have all the kernel-specific helpers from drgn/helpers/linux under
> tools/drgn/helpers/ in the kernel tree and the tools living under
> tools/drgn/<subsys>/ e.g. tools/drgn/bpf/.
> 
>>> [1] https://github.com/osandov/drgn/tree/master/drgn/helpers/linux
>>> [2] https://github.com/osandov/drgn/tree/master/examples/linux
> 
I can think of a few benefits of having this tool (and others like it)
in the drgn repository:

* Easier to keep in sync with new helpers/API changes
* More examples in one centralized place for people building new tools
* Potential to identify pain points in the API and possible new helpers

I think this would benefit the drgn project as a whole.

The downsides:

* More maintenance for me
* Tools will have to support multiple kernel versions (as opposed to
  only supporting the kernel that they shipped with)
* Less visibility for kernel developers

That second point is true of the helpers bundled with drgn anyways, so I
don't think it's a big deal. The third point will improve over time as
we get more people on the drgn train :)

I may come to regret the first point, but I think the upsides are worth
it. Andrey, feel free to submit a PR adding this to the drgn repository
under a new top-level tools/ directory.
Quentin Monnet Feb. 28, 2020, 12:51 p.m. UTC | #10
2020-02-26 18:32 UTC-0800 ~ Andrey Ignatov <rdna@fb.com>
> drgn is a debugger that reads kernel memory and uses DWARF to get types
> and symbols. See [1], [2] and [3] for more details on drgn.
> 
> Since drgn operates on kernel memory it has access to kernel internals
> that user space doesn't. It allows to get extended info about various
> kernel data structures.
> 
> Introduce bpf.py drgn script to list BPF programs and maps and their
> properties unavailable to user space via kernel API.
> 
> The main use-case bpf.py covers is to show BPF programs attached to
> other BPF programs via freplace/fentry/fexit mechanisms introduced
> recently. There is no user-space API to get this info and e.g. bpftool
> can only show all BPF programs but can't show if program A replaces a
> function in program B.
> 

[...]

> 
> Signed-off-by: Andrey Ignatov <rdna@fb.com>
> ---
>   tools/bpf/bpf.py | 149 +++++++++++++++++++++++++++++++++++++++++++++++
>   1 file changed, 149 insertions(+)
>   create mode 100755 tools/bpf/bpf.py
> 
> diff --git a/tools/bpf/bpf.py b/tools/bpf/bpf.py
> new file mode 100755
> index 000000000000..a00d112c0486
> --- /dev/null
> +++ b/tools/bpf/bpf.py
> @@ -0,0 +1,149 @@
> +#!/usr/bin/env drgn
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +#
> +# Copyright (c) 2020 Facebook
> +
> +DESCRIPTION = """
> +drgn script to list BPF programs or maps and their properties
> +unavailable via kernel API.
> +
> +See https://github.com/osandov/drgn/ for more details on drgn.
> +"""
> +
> +import argparse
> +import sys
> +
> +from drgn.helpers import enum_type_to_class
> +from drgn.helpers.linux import (
> +    bpf_map_for_each,
> +    bpf_prog_for_each,
> +    hlist_for_each_entry,
> +)
> +
> +
> +BpfMapType = enum_type_to_class(prog.type("enum bpf_map_type"), "BpfMapType")
> +BpfProgType = enum_type_to_class(prog.type("enum bpf_prog_type"), "BpfProgType")
> +BpfProgTrampType = enum_type_to_class(
> +    prog.type("enum bpf_tramp_prog_type"), "BpfProgTrampType"
> +)
> +BpfAttachType = enum_type_to_class(
> +    prog.type("enum bpf_attach_type"), "BpfAttachType"
> +)

Hi Andrey, the script looks neat, thanks for this work!

I tried to run it on my system. Because my kernel is 5.3 and does not 
have "enum bpf_tramp_prog_type", the script crashes on the above 
assignments. But even without that enum, it could be possible to print 
program and map ids and types (even if we don't show the trampolines).

Do you think it would be worth adding error handling on that block, 
something like:

     try:
         BpfMapType = ...
         BpfProgType = ...
         BpfProgTrampType = ...
         BpfAttachType = ...
     except LookupError as e:
         print(e) # Possibly add a hint as kernel being too old?

I understand that printing the BPF extensions is the main interest of 
the script, I'm just thinking it would be nice to use it / tweak it even 
if not on the latest kernel. What do you think?

> +
> +
> +def get_btf_name(btf, btf_id):
> +    type_ = btf.types[btf_id]
> +    if type_.name_off < btf.hdr.str_len:
> +        return btf.strings[type_.name_off].address_of_().string_().decode()
> +    return ""
> +
> +
> +def get_prog_btf_name(bpf_prog):
> +    aux = bpf_prog.aux
> +    if aux.btf:
> +        # func_info[0] points to BPF program function itself.
> +        return get_btf_name(aux.btf, aux.func_info[0].type_id)
> +    return ""
> +
> +
> +def get_prog_name(bpf_prog):
> +    return get_prog_btf_name(bpf_prog) or bpf_prog.aux.name.string_().decode()
> +
> +
> +def attach_type_to_tramp(attach_type):
> +    at = BpfAttachType(attach_type)
> +
> +    if at == BpfAttachType.BPF_TRACE_FENTRY:
> +        return BpfProgTrampType.BPF_TRAMP_FENTRY
> +
> +    if at == BpfAttachType.BPF_TRACE_FEXIT:
> +        return BpfProgTrampType.BPF_TRAMP_FEXIT
> +
> +    return BpfProgTrampType.BPF_TRAMP_REPLACE
> +
> +
> +def get_linked_func(bpf_prog):
> +    kind = attach_type_to_tramp(bpf_prog.expected_attach_type)
> +
> +    linked_prog = bpf_prog.aux.linked_prog
> +    linked_btf_id = bpf_prog.aux.attach_btf_id
> +
> +    linked_prog_id = linked_prog.aux.id.value_()
> +    linked_name = "{}->{}()".format(
> +        get_prog_name(linked_prog),
> +        get_btf_name(linked_prog.aux.btf, linked_btf_id),
> +    )
> +
> +    return "{}->{}: {} {}".format(
> +        linked_prog_id, linked_btf_id.value_(), kind.name, linked_name
> +    )
> +
> +
> +def get_tramp_progs(bpf_prog):
> +    tr = bpf_prog.aux.trampoline
> +    if not tr:
> +        return

Same observation here, I solved it with

     try:
         tr = bpf_prog.aux.trampoline
         if not tr:
             return
     except AttributeError as e:
         print(e)
         return

Best regards,
Quentin
Andrey Ignatov Feb. 28, 2020, 8:11 p.m. UTC | #11
Omar Sandoval <osandov@fb.com> [Thu, 2020-02-27 14:19 -0800]:
> On 2/27/20 1:32 PM, Daniel Borkmann wrote:
> > On 2/27/20 10:11 PM, Daniel Borkmann wrote:
> >> [ +tj ]
> >>
> >> On 2/27/20 7:26 PM, Andrey Ignatov wrote:
> >>> Stanislav Fomichev <sdf@fomichev.me> [Thu, 2020-02-27 10:01 -0800]:
> >>>> On 02/26, Andrey Ignatov wrote:
> >>>>> drgn is a debugger that reads kernel memory and uses DWARF to get types
> >>>>> and symbols. See [1], [2] and [3] for more details on drgn.
> >>>>>
> >>>>> Since drgn operates on kernel memory it has access to kernel internals
> >>>>> that user space doesn't. It allows to get extended info about various
> >>>>> kernel data structures.
> >>>>>
> >>>>> Introduce bpf.py drgn script to list BPF programs and maps and their
> >>>>> properties unavailable to user space via kernel API.
> >>>> Any reason this is not pushed to https://github.com/osandov/drgn/ ?
> >>>> I have a bunch of networking helpers for drgn as well, but I was
> >>>> thinking about contributing them to the drgn github, not the kernel.
> >>>> IMO, seems like a better place to consolidate all drgn stuff.
> >>>
> >>> I have this part in the commit message:
> >>>
> >>>>> The script can be sent to drgn repo where it's easier to maintain its
> >>>>> "drgn-ness", but in kernel tree it should be easier to maintain BPF
> >>>>> functionality itself what can be more important in this case.
> >>>
> >>> That's being said it's debatable which place is better and I'm still
> >>> trying to figure it out myself since, from what i see, there is no
> >>> widely adopted practice.
> >>>
> >>> I've been contributing to drgn as well mostly in two forms:
> >>> * helpers [1];
> >>> * examples [2]
> >>>
> >>> And so far I used examples/ dir as a place to keep small useful "tools"
> >>> (tcp_sock.py, cgroup.py, bpf.py).
> >>>
> >>> But there is no place for bigger "scripts" or "tools" in drgn (yet?). On
> >>> the other hand I see two drgn scripts in kernel tree already:
> >>> * tools/cgroup/iocost_coef_gen.py
> >>> * tools/cgroup/iocost_monitor.py
> >>>
> >>> So maybe it's time to discuss where to keep tools like this in the
> >>> future.
> >>>
> >>> In this specifc case I'd love to see feedback from Omar and BPF
> >>> maintainers.
> >>
> >> I can certainly see both sides given that drgn tools have been added to
> >> tools/cgroup/ already. I presume if so, then these could live in tools/drgn/
> >> which would also make it more clear what is needed to run as dependency
> >> plus there should be be a proper high-level readme to document what developers
> >> need to run in order to run them. But from looking at [1], I can also see that
> >> those scripts would depend on new helpers being added/updated/deleted, so it
> >> might be easier to add drgn/tools/ directory where scripts could be updated
> >> in one go with updates to drgn helpers. Either way, I think it would be nice
> >> to add documentation somewhere for getting people started.
> > 
> > One example that should definitely be avoided is 9ea37e24d4a9 ("iocost: Fix
> > iocost_monitor.py due to helper type mismatch") due to both living in separate
> > places. A third option to think about (if this is to be adapted by more subsystems)
> > could be to have all the kernel-specific helpers from drgn/helpers/linux under
> > tools/drgn/helpers/ in the kernel tree and the tools living under
> > tools/drgn/<subsys>/ e.g. tools/drgn/bpf/.
> > 
> >>> [1] https://github.com/osandov/drgn/tree/master/drgn/helpers/linux
> >>> [2] https://github.com/osandov/drgn/tree/master/examples/linux
> > 
> I can think of a few benefits of having this tool (and others like it)
> in the drgn repository:
> 
> * Easier to keep in sync with new helpers/API changes
> * More examples in one centralized place for people building new tools
> * Potential to identify pain points in the API and possible new helpers
> 
> I think this would benefit the drgn project as a whole.
> 
> The downsides:
> 
> * More maintenance for me
> * Tools will have to support multiple kernel versions (as opposed to
>   only supporting the kernel that they shipped with)
> * Less visibility for kernel developers
> 
> That second point is true of the helpers bundled with drgn anyways, so I
> don't think it's a big deal. The third point will improve over time as
> we get more people on the drgn train :)
> 
> I may come to regret the first point, but I think the upsides are worth
> it. Andrey, feel free to submit a PR adding this to the drgn repository
> under a new top-level tools/ directory.

Daniel, Omar, thanks for your input. This all makes sense and it seems
we're coming to an agreement that it'll be easier to support tools like
this in drgn repo. That works for me and with this in mind I see the
followig way forward:

- I'll submit PR to drgn repo to create a new tools/ directory and place
  the script there using the bpf_inspect.py name suggested by Andrii.
  This way the script will be in sync with all the recent changes to 
  drgn helpers;

- As soon as PR is merged I'll add documentation to kernel tree to make
  folks aware that such a tool exists, if there are any recommendation
  where to add the documentation to I'd appreciate it.

- I'll try to make the script less dependent on kernel version
  (Quentin's feedba) since it'll exist separate from the kernel
  sources.


Hope it works for everyone, please lmk if it doesn't for some reason of
if I missed something. Thanks.
Andrey Ignatov Feb. 28, 2020, 8:15 p.m. UTC | #12
Quentin Monnet <quentin@isovalent.com> [Fri, 2020-02-28 04:51 -0800]:
> 2020-02-26 18:32 UTC-0800 ~ Andrey Ignatov <rdna@fb.com>
> > drgn is a debugger that reads kernel memory and uses DWARF to get types
> > and symbols. See [1], [2] and [3] for more details on drgn.
> > 
> > Since drgn operates on kernel memory it has access to kernel internals
> > that user space doesn't. It allows to get extended info about various
> > kernel data structures.
> > 
> > Introduce bpf.py drgn script to list BPF programs and maps and their
> > properties unavailable to user space via kernel API.
> > 
> > The main use-case bpf.py covers is to show BPF programs attached to
> > other BPF programs via freplace/fentry/fexit mechanisms introduced
> > recently. There is no user-space API to get this info and e.g. bpftool
> > can only show all BPF programs but can't show if program A replaces a
> > function in program B.
> > 
> 
> [...]
> 
> > 
> > Signed-off-by: Andrey Ignatov <rdna@fb.com>
> > ---
> >   tools/bpf/bpf.py | 149 +++++++++++++++++++++++++++++++++++++++++++++++
> >   1 file changed, 149 insertions(+)
> >   create mode 100755 tools/bpf/bpf.py
> > 
> > diff --git a/tools/bpf/bpf.py b/tools/bpf/bpf.py
> > new file mode 100755
> > index 000000000000..a00d112c0486
> > --- /dev/null
> > +++ b/tools/bpf/bpf.py
> > @@ -0,0 +1,149 @@
> > +#!/usr/bin/env drgn
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > +#
> > +# Copyright (c) 2020 Facebook
> > +
> > +DESCRIPTION = """
> > +drgn script to list BPF programs or maps and their properties
> > +unavailable via kernel API.
> > +
> > +See https://github.com/osandov/drgn/ for more details on drgn.
> > +"""
> > +
> > +import argparse
> > +import sys
> > +
> > +from drgn.helpers import enum_type_to_class
> > +from drgn.helpers.linux import (
> > +    bpf_map_for_each,
> > +    bpf_prog_for_each,
> > +    hlist_for_each_entry,
> > +)
> > +
> > +
> > +BpfMapType = enum_type_to_class(prog.type("enum bpf_map_type"), "BpfMapType")
> > +BpfProgType = enum_type_to_class(prog.type("enum bpf_prog_type"), "BpfProgType")
> > +BpfProgTrampType = enum_type_to_class(
> > +    prog.type("enum bpf_tramp_prog_type"), "BpfProgTrampType"
> > +)
> > +BpfAttachType = enum_type_to_class(
> > +    prog.type("enum bpf_attach_type"), "BpfAttachType"
> > +)
> 
> Hi Andrey, the script looks neat, thanks for this work!
> 
> I tried to run it on my system. Because my kernel is 5.3 and does not have
> "enum bpf_tramp_prog_type", the script crashes on the above assignments. But
> even without that enum, it could be possible to print program and map ids
> and types (even if we don't show the trampolines).
> 
> Do you think it would be worth adding error handling on that block,
> something like:
> 
>     try:
>         BpfMapType = ...
>         BpfProgType = ...
>         BpfProgTrampType = ...
>         BpfAttachType = ...
>     except LookupError as e:
>         print(e) # Possibly add a hint as kernel being too old?
> 
> I understand that printing the BPF extensions is the main interest of the
> script, I'm just thinking it would be nice to use it / tweak it even if not
> on the latest kernel. What do you think?

Hi Quentin,

Thanks for feedback. That's a nice usability improvement indeed.

I'll add something like this and tag you on the github PR (since we're
coming to the conclusion that drgn repo is a better place for it).

> > +
> > +
> > +def get_btf_name(btf, btf_id):
> > +    type_ = btf.types[btf_id]
> > +    if type_.name_off < btf.hdr.str_len:
> > +        return btf.strings[type_.name_off].address_of_().string_().decode()
> > +    return ""
> > +
> > +
> > +def get_prog_btf_name(bpf_prog):
> > +    aux = bpf_prog.aux
> > +    if aux.btf:
> > +        # func_info[0] points to BPF program function itself.
> > +        return get_btf_name(aux.btf, aux.func_info[0].type_id)
> > +    return ""
> > +
> > +
> > +def get_prog_name(bpf_prog):
> > +    return get_prog_btf_name(bpf_prog) or bpf_prog.aux.name.string_().decode()
> > +
> > +
> > +def attach_type_to_tramp(attach_type):
> > +    at = BpfAttachType(attach_type)
> > +
> > +    if at == BpfAttachType.BPF_TRACE_FENTRY:
> > +        return BpfProgTrampType.BPF_TRAMP_FENTRY
> > +
> > +    if at == BpfAttachType.BPF_TRACE_FEXIT:
> > +        return BpfProgTrampType.BPF_TRAMP_FEXIT
> > +
> > +    return BpfProgTrampType.BPF_TRAMP_REPLACE
> > +
> > +
> > +def get_linked_func(bpf_prog):
> > +    kind = attach_type_to_tramp(bpf_prog.expected_attach_type)
> > +
> > +    linked_prog = bpf_prog.aux.linked_prog
> > +    linked_btf_id = bpf_prog.aux.attach_btf_id
> > +
> > +    linked_prog_id = linked_prog.aux.id.value_()
> > +    linked_name = "{}->{}()".format(
> > +        get_prog_name(linked_prog),
> > +        get_btf_name(linked_prog.aux.btf, linked_btf_id),
> > +    )
> > +
> > +    return "{}->{}: {} {}".format(
> > +        linked_prog_id, linked_btf_id.value_(), kind.name, linked_name
> > +    )
> > +
> > +
> > +def get_tramp_progs(bpf_prog):
> > +    tr = bpf_prog.aux.trampoline
> > +    if not tr:
> > +        return
> 
> Same observation here, I solved it with
> 
>     try:
>         tr = bpf_prog.aux.trampoline
>         if not tr:
>             return
>     except AttributeError as e:
>         print(e)
>         return

Yep, sounds good.  Will address in v2 on github as well. Thanks!
Andrey Ignatov Feb. 28, 2020, 9:29 p.m. UTC | #13
Andrey Ignatov <rdna@fb.com> [Fri, 2020-02-28 12:11 -0800]:
> Omar Sandoval <osandov@fb.com> [Thu, 2020-02-27 14:19 -0800]:
> > On 2/27/20 1:32 PM, Daniel Borkmann wrote:
> > > On 2/27/20 10:11 PM, Daniel Borkmann wrote:
> > >> [ +tj ]
> > >>
> > >> On 2/27/20 7:26 PM, Andrey Ignatov wrote:
> > >>> Stanislav Fomichev <sdf@fomichev.me> [Thu, 2020-02-27 10:01 -0800]:
> > >>>> On 02/26, Andrey Ignatov wrote:

[...]

> > >>>
> > >>> In this specifc case I'd love to see feedback from Omar and BPF
> > >>> maintainers.
> > >>
> > >> I can certainly see both sides given that drgn tools have been added to
> > >> tools/cgroup/ already. I presume if so, then these could live in tools/drgn/
> > >> which would also make it more clear what is needed to run as dependency
> > >> plus there should be be a proper high-level readme to document what developers
> > >> need to run in order to run them. But from looking at [1], I can also see that
> > >> those scripts would depend on new helpers being added/updated/deleted, so it
> > >> might be easier to add drgn/tools/ directory where scripts could be updated
> > >> in one go with updates to drgn helpers. Either way, I think it would be nice
> > >> to add documentation somewhere for getting people started.
> > > 
> > > One example that should definitely be avoided is 9ea37e24d4a9 ("iocost: Fix
> > > iocost_monitor.py due to helper type mismatch") due to both living in separate
> > > places. A third option to think about (if this is to be adapted by more subsystems)
> > > could be to have all the kernel-specific helpers from drgn/helpers/linux under
> > > tools/drgn/helpers/ in the kernel tree and the tools living under
> > > tools/drgn/<subsys>/ e.g. tools/drgn/bpf/.
> > > 
> > >>> [1] https://github.com/osandov/drgn/tree/master/drgn/helpers/linux
> > >>> [2] https://github.com/osandov/drgn/tree/master/examples/linux
> > > 
> > I can think of a few benefits of having this tool (and others like it)
> > in the drgn repository:
> > 
> > * Easier to keep in sync with new helpers/API changes
> > * More examples in one centralized place for people building new tools
> > * Potential to identify pain points in the API and possible new helpers
> > 
> > I think this would benefit the drgn project as a whole.
> > 
> > The downsides:
> > 
> > * More maintenance for me
> > * Tools will have to support multiple kernel versions (as opposed to
> >   only supporting the kernel that they shipped with)
> > * Less visibility for kernel developers
> > 
> > That second point is true of the helpers bundled with drgn anyways, so I
> > don't think it's a big deal. The third point will improve over time as
> > we get more people on the drgn train :)
> > 
> > I may come to regret the first point, but I think the upsides are worth
> > it. Andrey, feel free to submit a PR adding this to the drgn repository
> > under a new top-level tools/ directory.
> 
> Daniel, Omar, thanks for your input. This all makes sense and it seems
> we're coming to an agreement that it'll be easier to support tools like
> this in drgn repo. That works for me and with this in mind I see the
> followig way forward:
> 
> - I'll submit PR to drgn repo to create a new tools/ directory and place
>   the script there using the bpf_inspect.py name suggested by Andrii.
>   This way the script will be in sync with all the recent changes to 
>   drgn helpers;
> 
> - As soon as PR is merged I'll add documentation to kernel tree to make
>   folks aware that such a tool exists, if there are any recommendation
>   where to add the documentation to I'd appreciate it.
> 
> - I'll try to make the script less dependent on kernel version
>   (Quentin's feedba) since it'll exist separate from the kernel
>   sources.
> 
> 
> Hope it works for everyone, please lmk if it doesn't for some reason of
> if I missed something. Thanks.

For anyone following: https://github.com/osandov/drgn/pull/49

Patch
diff mbox series

diff --git a/tools/bpf/bpf.py b/tools/bpf/bpf.py
new file mode 100755
index 000000000000..a00d112c0486
--- /dev/null
+++ b/tools/bpf/bpf.py
@@ -0,0 +1,149 @@ 
+#!/usr/bin/env drgn
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+#
+# Copyright (c) 2020 Facebook
+
+DESCRIPTION = """
+drgn script to list BPF programs or maps and their properties
+unavailable via kernel API.
+
+See https://github.com/osandov/drgn/ for more details on drgn.
+"""
+
+import argparse
+import sys
+
+from drgn.helpers import enum_type_to_class
+from drgn.helpers.linux import (
+    bpf_map_for_each,
+    bpf_prog_for_each,
+    hlist_for_each_entry,
+)
+
+
+BpfMapType = enum_type_to_class(prog.type("enum bpf_map_type"), "BpfMapType")
+BpfProgType = enum_type_to_class(prog.type("enum bpf_prog_type"), "BpfProgType")
+BpfProgTrampType = enum_type_to_class(
+    prog.type("enum bpf_tramp_prog_type"), "BpfProgTrampType"
+)
+BpfAttachType = enum_type_to_class(
+    prog.type("enum bpf_attach_type"), "BpfAttachType"
+)
+
+
+def get_btf_name(btf, btf_id):
+    type_ = btf.types[btf_id]
+    if type_.name_off < btf.hdr.str_len:
+        return btf.strings[type_.name_off].address_of_().string_().decode()
+    return ""
+
+
+def get_prog_btf_name(bpf_prog):
+    aux = bpf_prog.aux
+    if aux.btf:
+        # func_info[0] points to BPF program function itself.
+        return get_btf_name(aux.btf, aux.func_info[0].type_id)
+    return ""
+
+
+def get_prog_name(bpf_prog):
+    return get_prog_btf_name(bpf_prog) or bpf_prog.aux.name.string_().decode()
+
+
+def attach_type_to_tramp(attach_type):
+    at = BpfAttachType(attach_type)
+
+    if at == BpfAttachType.BPF_TRACE_FENTRY:
+        return BpfProgTrampType.BPF_TRAMP_FENTRY
+
+    if at == BpfAttachType.BPF_TRACE_FEXIT:
+        return BpfProgTrampType.BPF_TRAMP_FEXIT
+
+    return BpfProgTrampType.BPF_TRAMP_REPLACE
+
+
+def get_linked_func(bpf_prog):
+    kind = attach_type_to_tramp(bpf_prog.expected_attach_type)
+
+    linked_prog = bpf_prog.aux.linked_prog
+    linked_btf_id = bpf_prog.aux.attach_btf_id
+
+    linked_prog_id = linked_prog.aux.id.value_()
+    linked_name = "{}->{}()".format(
+        get_prog_name(linked_prog),
+        get_btf_name(linked_prog.aux.btf, linked_btf_id),
+    )
+
+    return "{}->{}: {} {}".format(
+        linked_prog_id, linked_btf_id.value_(), kind.name, linked_name
+    )
+
+
+def get_tramp_progs(bpf_prog):
+    tr = bpf_prog.aux.trampoline
+    if not tr:
+        return
+
+    if tr.extension_prog:
+        yield tr.extension_prog
+    else:
+        for head in tr.progs_hlist:
+            for tramp_aux in hlist_for_each_entry(
+                "struct bpf_prog_aux", head, "tramp_hlist"
+            ):
+                yield tramp_aux.prog
+
+
+def list_bpf_progs(args, prog):
+    for bpf_prog in bpf_prog_for_each(prog):
+        id_ = bpf_prog.aux.id.value_()
+        type_ = BpfProgType(bpf_prog.type).name
+        name = get_prog_name(bpf_prog)
+
+        linked = ", ".join(
+            [get_linked_func(p) for p in get_tramp_progs(bpf_prog)]
+        )
+        if linked:
+            linked = " linked:[{}]".format(linked)
+
+        print("{:>6}: {:32} {:32}{}".format(id_, type_, name, linked))
+
+
+def list_bpf_maps(args, prog):
+    for map_ in bpf_map_for_each(prog):
+        id_ = map_.id.value_()
+        type_ = BpfMapType(map_.map_type).name
+        name_ = map_.name.string_().decode()
+
+        print("{:>6}: {:32} {}".format(id_, type_, name_))
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description=DESCRIPTION, formatter_class=argparse.RawTextHelpFormatter
+    )
+
+    subparsers = parser.add_subparsers(
+        title="subcommands", dest="subparser_name"
+    )
+
+    prog_parser = subparsers.add_parser(
+        "prog", aliases=["p"], help="list BPF programs"
+    )
+    prog_parser.set_defaults(func=list_bpf_progs)
+
+    map_parser = subparsers.add_parser(
+        "map", aliases=["m"], help="list BPF maps"
+    )
+    map_parser.set_defaults(func=list_bpf_maps)
+
+    args = parser.parse_args()
+    if not args.subparser_name:
+        parser.print_help()
+        sys.exit(2)
+
+    args.func(args, prog)  # prog is global drgn.Program provided by drgn.
+
+
+if __name__ == "__main__":
+    main()