eBPF Talk: 自制查看 bpf prog 反汇编的工具

文摘   2024-10-28 08:10   新加坡  

目前,我知道的查看 bpf prog 反汇编的办法有:

  1. 使用 bpftool prog dump jited
  2. 使用 gdb -q -c /proc/kcore -ex 'disas/r 0xffffffffc00a6188,+0x143' -ex 'quit'
  3. 使用 drgn contrib/bpf_inspec.py

这些办法各有优劣。

1. bpftool prog dump jited

bpftool 查看反汇编的办法如下:

# bpftool p d j i 23 linum opcodes
int fentry_xdp(unsigned long long * ctx):
bpf_prog_52d82017b7a0a424_fentry_xdp:
; int BPF_PROG(fentry_xdp, struct xdp_buff *xdp) [file:./xdp.c line_num:30 line_col:0]
   0:   nopl    (%rax,%rax)
    0f 1f 44 00 00
   5:   nop
    66 90
   7:   pushq   %rbp
    55
   8:   movq    %rsp, %rbp
    48 89 e5
   b:   subq    $24, %rsp
    48 81 ec 18 00 00 00
  12:   pushq   %rbx
    53
  13:   pushq   %r13
    41 55
  15:   pushq   %r14
    41 56
  17:   pushq   %r15
    41 57
  19:   movq    %rdi, %rbx
    48 89 fb
  1c:   xorl    %edi, %edi
    31 ff
; int BPF_PROG(fentry_xdp, struct xdp_buff *xdp) [file:./xdp.c line_num:30 line_col:5]
  1e:   movq    (%rbx), %r14
    4c 8b 73 00
  22:   movq    %r14, %rdx
    4c 89 f2
  25:   addq    %rdi, %rdx
    48 01 fa
  28:   movq    %rbp, %rdi
    48 89 ef
;  [file:./xdp.c line_num:0 line_col:0]
  2b:   addq    $-16, %rdi
    48 83 c7 f0
; struct ethhdr *eth = (void *)(long)BPF_CORE_READ(xdp, data); [file:./xdp.c line_num:15 line_col:40]
  2f:   movl    $8, %esi
    be 08 00 00 00
  34:   callq   0xffffffffec9de688
    ...
; bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &ev, sizeof(ev)); [file:./../headers/lib_xdp_tc.h line_num:41 line_col:5]
 118:   movq    %rbx, %rdi
    48 89 df
 11b:   movabsq $-105200468817920, %rsi
    48 be 00 08 6d 1b 52 a0 ff ff
 125:   movl    $4294967295, %edx
    ba ff ff ff ff
 12a:   movq    %r14, %rcx
    4c 89 f1
 12d:   movl    $12, %r8d
    41 b8 0c 00 00 00
 133:   callq   0xffffffffec9dff08
    e8 d0 fd 9d ec
; int BPF_PROG(fentry_xdp, struct xdp_buff *xdp) [file:./xdp.c line_num:30 line_col:5]
 138:   xorl    %eax, %eax
    31 c0
 13a:   popq    %r15
    41 5f
 13c:   popq    %r14
    41 5e
 13e:   popq    %r13
    41 5d
 140:   popq    %rbx
    5b
 141:   leave
    c9
 142:   retq
    c3
 143:   int3
    cc

通过指定 linumopcodes,可以看到行号信息、源代码和原始的机器码。

P.S. bpftool 里计算的 callq 地址是错误的:Wrong callq address displayed[1]

2. gdb -q -c /proc/kcore -ex 'disas/r 0xffffffffc00a6188,+0x143' -ex 'quit'

gdb 查看反汇编的办法如下:

# grep fentry_xdp /proc/kallsyms
ffffffffc00a6188 t bpf_prog_52d82017b7a0a424_fentry_xdp [bpf]
# gdb -q -c /proc/kcore -ex 'disas/r 0xffffffffc00a6188,+0x143' -ex 'quit'
Dump of assembler code from 0xffffffffc00a6188 to 0xffffffffc00a62cb:
   0xffffffffc01062d8:  0f 1f 44 00 00      nopl   0x0(%rax,%rax,1)
   0xffffffffc01062dd:  66 90               xchg   %ax,%ax
   0xffffffffc01062df:  55                  push   %rbp
   0xffffffffc01062e0:  48 89 e5            mov    %rsp,%rbp
   0xffffffffc01062e3:  48 81 ec 18 00 00 00    sub    $0x18,%rsp
   0xffffffffc01062ea:  53                  push   %rbx
   0xffffffffc01062eb:  41 55               push   %r13
   0xffffffffc01062ed:  41 56               push   %r14
   0xffffffffc01062ef:  41 57               push   %r15
   0xffffffffc01062f1:  48 89 fb            mov    %rdi,%rbx
   0xffffffffc01062f4:  31 ff               xor    %edi,%edi
   0xffffffffc01062f6:  4c 8b 73 00         mov    0x0(%rbx),%r14
   0xffffffffc01062fa:  4c 89 f2            mov    %r14,%rdx
   0xffffffffc01062fd:  48 01 fa            add    %rdi,%rdx
   0xffffffffc0106300:  48 89 ef            mov    %rbp,%rdi
   0xffffffffc0106303:  48 83 c7 f0         add    $0xfffffffffffffff0,%rdi
   0xffffffffc0106307:  be 08 00 00 00      mov    $0x8,%esi
   0xffffffffc010630c:  e8 4f e6 9d ec      call   0xffffffffacae4960
   ...
   0xffffffffc01063f0:  48 89 df            mov    %rbx,%rdi
   0xffffffffc01063f3:  48 be 00 08 6d 1b 52 a0 ff ff   movabs $0xffffa0521b6d0800,%rsi
   0xffffffffc01063fd:  ba ff ff ff ff      mov    $0xffffffff,%edx
   0xffffffffc0106402:  4c 89 f1            mov    %r14,%rcx
   0xffffffffc0106405:  41 b8 0c 00 00 00   mov    $0xc,%r8d
   0xffffffffc010640b:  e8 d0 fd 9d ec      call   0xffffffffacae61e0
   0xffffffffc0106410:  31 c0               xor    %eax,%eax
   0xffffffffc0106412:  41 5f               pop    %r15
   0xffffffffc0106414:  41 5e               pop    %r14
   0xffffffffc0106416:  41 5d               pop    %r13
   0xffffffffc0106418:  5b                  pop    %rbx
   0xffffffffc0106419:  c9                  leave
   0xffffffffc010641a:  c3                  ret

3. drgn contrib/bpf_inspec.py

drgn 的 PR Proposal: contrib/bpf_inspect.py: disas bpf prog with capstone[2] 里,可以查看 bpf prog 的反汇编:

# drgn ./contrib/bpf_inspect.py i
For helptype help(drgn).
>>> import drgn
>>> from drgn import NULL, Object, cast, container_of, execscript, offsetof, reinterpret, sizeof, stack_trace
>>> from drgn.helpers.common import *
>>> from drgn.helpers.linux import *
>>> p = get_bpf_prog_by_id(25)
>>> print("\n".join(p.disas()))
0xffffffffc01062d8: 0f 1f 44 00 00      nop dword ptr [rax + rax]
0xffffffffc01062dd: 66 90               nop
0xffffffffc01062df: 55                  push    rbp
0xffffffffc01062e0: 48 89 e5            mov rbp, rsp
0xffffffffc01062e3: 48 81 ec 18 00 00 00    sub rsp, 0x18
0xffffffffc01062ea: 53                  push    rbx
0xffffffffc01062eb: 41 55               push    r13
0xffffffffc01062ed: 41 56               push    r14
0xffffffffc01062ef: 41 57               push    r15
0xffffffffc01062f1: 48 89 fb            mov rbx, rdi
0xffffffffc01062f4: 31 ff               xor edi, edi
0xffffffffc01062f6: 4c 8b 73 00         mov r14, qword ptr [rbx]
0xffffffffc01062fa: 4c 89 f2            mov rdx, r14
0xffffffffc01062fd: 48 01 fa            add rdx, rdi
0xffffffffc0106300: 48 89 ef            mov rdi, rbp
0xffffffffc0106303: 48 83 c7 f0         add rdi, -0x10
0xffffffffc0106307: be 08 00 00 00      mov esi, 8
0xffffffffc010630c: e8 4f e6 9d ec      call    0xffffffffacae4960
...
0xffffffffc01063f0: 48 89 df            mov rdi, rbx
0xffffffffc01063f3: 48 be 00 08 6d 1b 52 a0 ff ff   movabs  rsi, 0xffffa0521b6d0800
0xffffffffc01063fd: ba ff ff ff ff      mov edx, 0xffffffff
0xffffffffc0106402: 4c 89 f1            mov rcx, r14
0xffffffffc0106405: 41 b8 0c 00 00 00   mov r8d, 0xc
0xffffffffc010640b: e8 d0 fd 9d ec      call    0xffffffffacae61e0
0xffffffffc0106410: 31 c0               xor eax, eax
0xffffffffc0106412: 41 5f               pop r15
0xffffffffc0106414: 41 5e               pop r14
0xffffffffc0106416: 41 5d               pop r13
0xffffffffc0106418: 5b                  pop rbx
0xffffffffc0106419: c9                  leave
0xffffffffc010641a: c3                  ret
0xffffffffc010641b: cc                  int3

好吧,drgn 的效果跟 gdb 的效果差不多;不过,却要依赖内核 dbgsym 文件。

自制查看 bpf prog 反汇编的工具

在开发使用 LBR 跟踪 bpf prog 内部一些细节的工具 bpflbr[3] 的过程中,顺手将查看 bpf prog 反汇编的功能先开发出来了:

# ./bpflbr -p 25 --dump-jited
; ./xdp.c:30:0 int BPF_PROG(fentry_xdp, struct xdp_buff *xdp)
0xffffffffc01062d8: 0f 1f 44 00 00      nopl    (%rax, %rax)
0xffffffffc01062dd: 66 90               nop
0xffffffffc01062df: 55                  pushq   %rbp
0xffffffffc01062e0: 48 89 e5            movq    %rsp, %rbp
0xffffffffc01062e3: 48 81 ec 18 00 00 00    subq    $0x18, %rsp
0xffffffffc01062ea: 53                  pushq   %rbx
0xffffffffc01062eb: 41 55               pushq   %r13
0xffffffffc01062ed: 41 56               pushq   %r14
0xffffffffc01062ef: 41 57               pushq   %r15
0xffffffffc01062f1: 48 89 fb            movq    %rdi, %rbx
0xffffffffc01062f4: 31 ff               xorl    %edi, %edi
; ./xdp.c:30:5 int BPF_PROG(fentry_xdp, struct xdp_buff *xdp)
0xffffffffc01062f6: 4c 8b 73 00         movq    (%rbx), %r14
0xffffffffc01062fa: 4c 89 f2            movq    %r14, %rdx
0xffffffffc01062fd: 48 01 fa            addq    %rdi, %rdx
0xffffffffc0106300: 48 89 ef            movq    %rbp, %rdi
; ./xdp.c:0:0
0xffffffffc0106303: 48 83 c7 f0         addq    $-0x10, %rdi
; ./xdp.c:15:40 struct ethhdr *eth = (void *)(long)BPF_CORE_READ(xdp, data);
0xffffffffc0106307: be 08 00 00 00      movl    $8, %esi
0xffffffffc010630c: e8 4f e6 9d ec      callq   0xffffffffacae4960
...
; ./../headers/lib_xdp_tc.h:41:5 bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &ev, sizeof(ev));
0xffffffffc01063f0: 48 89 df            movq    %rbx, %rdi
0xffffffffc01063f3: 48 be 00 08 6d 1b 52 a0 ff ff   movabsq $0xffffa0521b6d0800, %rsi
0xffffffffc01063fd: ba ff ff ff ff      movl    $0xffffffff, %edx
0xffffffffc0106402: 4c 89 f1            movq    %r14, %rcx
0xffffffffc0106405: 41 b8 0c 00 00 00   movl    $0xc, %r8d
0xffffffffc010640b: e8 d0 fd 9d ec      callq   0xffffffffacae61e0
; ./xdp.c:30:5 int BPF_PROG(fentry_xdp, struct xdp_buff *xdp)
0xffffffffc0106410: 31 c0               xorl    %eax, %eax
0xffffffffc0106412: 41 5f               popq    %r15
0xffffffffc0106414: 41 5e               popq    %r14
0xffffffffc0106416: 41 5d               popq    %r13
0xffffffffc0106418: 5b                  popq    %rbx
0xffffffffc0106419: c9                  leave
0xffffffffc010641a: c3                  retq
0xffffffffc010641b: cc                  int3

保持跟 gdb/drgn 同样效果的同时,也跟 bpftool 一样提供了行号信息和源代码;而且,并不需要内核 dbgsym 文件。

总结

bpftool 是最方便的,但计算 callq 地址有问题;gdb 用来查看反汇编略繁琐;drgn 用来查看反汇编需要内核 dbgsym 文件。

不需要内核 dbgsym 文件,又想要行号信息和源代码,那就用 bpflbr 吧。

参考资料
[1]

Wrong callq address displayed: https://github.com/libbpf/bpftool/issues/109

[2]

Proposal: contrib/bpf_inspect.py: disas bpf prog with capstone: https://github.com/osandov/drgn/pull/409

[3]

bpflbr: https://github.com/Asphaltt/bpflbr

eBPF Talk
专注于 eBPF 技术,以及 Linux 网络上的 eBPF 技术应用