一、实验Demo
1. 实验代码
ebpf_test.c:
#include <stdio.h> static __attribute__((__noinline__)) int my_add_fun(int x, int y) //若是不加 noinline 属性将会被编译器优化而无法probe { return x + y; } int main() { int a, b, ret; ret = scanf("%d %d", &a, &b); if (ret < 0) { printf("invalid params\n"); return -1; } ret = my_add_fun(a, b); printf("my_add_fun result is %d\n", ret); return ret; }
Android.mk:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= ebpf_test.c LOCAL_MODULE:= ebpf_test_add_no_inline #LOCAL_C_FLAGS += -g -O0 LOCAL_SHARED_LIBRARIES := libc include $(BUILD_EXECUTABLE)
若是不加 noinline 修饰 my_add_fun(),这个函数将无法被探测到,只有三个探测点,并没有 my_add_fun。
root@localhost:/# bpftrace -l 'u:/data/local/tmp/ebpf_test_1:*' uprobe:/data/local/tmp/ebpf_test_1:_start uprobe:/data/local/tmp/ebpf_test_1:_start_main uprobe:/data/local/tmp/ebpf_test_1:main
2. 进行测试,发现打印出来的栈回溯都是函数地址,而没有函数名。
root@localhost:/# bpftrace -e 'uprobe:/data/local/tmp/ebpf_test_no_inline:add {printf("ustack: %s\n", ustack());}' Attaching 1 probe... ustack: 0x652d3bc0f0 0x7218700c80 0x652d3bc058
3. 下面解决用户栈回溯只有地址没有函数符号的问题
(1) 先运行 ebpf_test_add_no_inline 这个测试elf文件:
(2) 开个新终端得到其其对应的maps
/data/local/tmp # cat /proc/28226/maps 56b6889000-56b688a000 r--p 00000000 fe:33 100895 /data/local/tmp/ebpf_test_add_no_inline 56b688a000-56b688b000 r-xp 00001000 fe:33 100895 /data/local/tmp/ebpf_test_add_no_inline //保存 ... 7ad4488000-7ad44ce000 r--p 00000000 07:f0 38 /apex/com.android.runtime/lib64/bionic/libc.so 7ad44ce000-7ad457a000 r-xp 00046000 07:f0 38 /apex/com.android.runtime/lib64/bionic/libc.so //保存 ...
(3) 启动uprobe后, 在输入 1 2 回车,进行测试:
/data/local/tmp # ./ebpf_test_add_no_inline 1 2 my_add_fun result is 3 root@localhost:/# bpftrace -e 'uprobe:/data/local/tmp/ebpf_test_add_no_inline:my_add_fun {printf("ustack: %s\n", ustack());}' Attaching 1 probe... ustack: 0x56b688a0f0 0x7ad44fdc80 0x56b688a058
这样就得到和maps对应的函数地址了。
(4) 根据 ustack 打印出来的地址找到对应的是什么段和在这个段内的偏移:
//地址 0x56b688a0f0 对应下面这个段,偏移地址为 0xf0 56b688a000-56b688b000 r-xp 00001000 fe:33 100895 /data/local/tmp/ebpf_test_add_no_inline //地址 0x7ad44fdc80 对应的是下面这个库,偏移地址为 0x2fc80 7ad44ce000-7ad457a000 r-xp 00046000 07:f0 38 /apex/com.android.runtime/lib64/bionic/libc.so //地址 0x56b688a058 对应的是下面这个库,偏移地址为 0x58 56b688a000-56b688b000 r-xp 00001000 fe:33 100895 /data/local/tmp/ebpf_test_add_no_inline
(5) 之后找各段代码段起始偏移:
root@localhost:/# llvm-objdump-11 -d -D /data/local/tmp/ebpf_test_add_no_inline ... Disassembly of section .text: 0000000000001000 <_start>: //其实也可以直接找到函数名: 00000000000010f0 <my_add_fun>: 10f0: d503245f bti c 10f4: 0b000020 add w0, w1, w0 10f8: d65f03c0 ret root@localhost:/# llvm-objdump-11 -d -D /apex/com.android.runtime/lib64/bionic/libc.so Disassembly of section .text: 0000000000046000 <__on_dlclose>:
(6) 然后根据偏移找到对应的函数:
root@localhost:/# llvm-addr2line-11 -f -e /data/local/tmp/ebpf_test_add_no_inline 0x10f0 //0x1000+0xf0 my_add_fun vendor/mine/frameworks/cmd/ebpf_test/ebpf_test.c:5 root@localhost:/# llvm-addr2line-11 -f -e /data/local/tmp/ebpf_test_add_no_inline 0x75c80 //0x46000+0x2fc80 ?? ??:0 root@localhost:/# llvm-addr2line-11 -f -e /data/local/tmp/ebpf_test_add_no_inline 0x1058 //0x1000+0x58 main vendor/mine/frameworks/cmd/ebpf_test/ebpf_test.c:9
(7) 因此整理后的调用栈为:
root@localhost:/# bpftrace -e 'uprobe:/data/local/tmp/ebpf_test_add_no_inline:my_add_fun {printf("ustack: %s\n", ustack());}' Attaching 1 probe... ustack: my_add_fun 0x7ad44fdc80 main
4. ustack() 打印的调用栈中多出来的 0x7ad44fdc80 是啥?
使用 gdb 来 check,即使在被bpf给钩住的情况下,仍然只有2层调用栈。
(gdb) bt #0 my_add_fun (x=<optimized out>, y=<optimized out>) at vendor/mine/frameworks/cmd/ebpf_test/ebpf_test.c:5 #1 0x000000555555609c in main () at vendor/mine/frameworks/cmd/ebpf_test/ebpf_test.c:18
从 objdump 的 libc.so 中可以看到 0x75c80 位置对应的是函数结束位置的一条跳转指令:
0000000000075c18 <__libc_init>: 75c18: 3f 23 03 d5 hint #25 ... 75c7c: 60 02 3f d6 blr x19 75c80: 80 e5 01 94 bl 0xef280 <exit@plt> 00000000000ef280 <exit@plt>: //而 0xef280 位置对应的是这个 ef280: 30 00 00 f0 adrp x16, #28672 ...
5. 总结
两次bpftrace对比可以发现,每次执行,cat/proc/pid/maps 得到的函数地址会变,但是 objdump 出来的代码段起始地址和偏移却不会变。
二、追踪 surfaceflinger
1. 可执行文件位置
root@localhost:/# ps -AT | grep surfaceflinger 1655 1655 ? 00:02:07 surfaceflinger root@localhost:/# ls -l /proc/1655/exe lrwxrwxrwx. 1 system 1003 0 Oct 12 04:01 /proc/1655/exe -> /system/bin/surfaceflinger
2. 查看执行探测的函数
root@localhost:/# bpftrace -l 'u:/system/bin/surfaceflinger:*' ... uprobe:/system/bin/surfaceflinger:_ZTv0_n24_N7android3gui22IScreenCaptureListenerD1Ev //由于C++支持重载而出现奇怪的函数名 uprobe:/system/bin/surfaceflinger:_ZTv0_n24_N7android3gui16ISurfaceComposerD0Ev uprobe:/system/bin/surfaceflinger:_ZTv0_n24_N7android3gui16ISurfaceComposerD1Ev uprobe:/system/bin/surfaceflinger:_ZN17GrBufferAllocPool11createBlockEm //GrBufferAllocPool::createBlock ...
3. 探测测试
root@localhost:/# bpftrace -e 'uprobe:/system/bin/surfaceflinger:_ZN17GrBufferAllocPool11createBlockEm {printf("ustack: %s\n", ustack());}' Attaching 1 probe... ustack: GrBufferAllocPool::createBlock(unsigned long)+0 GrVertexBufferAllocPool::makeSpace(unsigned long, int, sk_sp<GrBuffer const>*, int*)+96 0x5774baccb8 skgpu::v1::OpsTask::onPrepare(GrOpFlushState*)+584 GrRenderTask::prepare(GrOpFlushState*)+124 GrDrawingManager::executeRenderTasks(GrOpFlushState*)+100 GrDrawingManager::flush(SkSpan<GrSurfaceProxy*>, SkSurface::BackendSurfaceAccess, GrFlushInfo const&, GrBackendSurfaceMutableState const*)+2212 GrDrawingManager::flushSurfaces(SkSpan<GrSurfaceProxy*>, SkSurface::BackendSurfaceAccess, GrFlushInfo const&, GrBackendSurfaceMutableState const*)+168 GrDirectContextPriv::flushSurfaces(SkSpan<GrSurfaceProxy*>, SkSurface::BackendSurfaceAccess, GrFlushInfo const&, GrBackendSurfaceMutableState const*)+288 SkSurface_Gpu::onFlush(SkSurface::BackendSurfaceAccess, GrFlushInfo const&, GrBackendSurfaceMutableState const*)+156 SkSurface::flush()+68 0x5774db3054 0x5774dac4c8 //只有这个能解析出 0x5774da80d8 0x5774dab2e8 __pthread_start(void*)+212 __start_thread+72
4. 处理缺少函数符号的地址
root@localhost:/# cat /proc/1655/maps > /data/local/tmp/sf_maps.txt //上面的符号地址都在这个map段中,偏移分别为 0x448cb8 0x64f054 0x6484c8 0x6440d8 0x6472e8 5774764000-5774de2000 r-xp 00272000 fe:11 6573696 /system/bin/surfaceflinger root@localhost:/# llvm-objdump-11 -d -D /system/bin/surfaceflinger > /data/local/tmp/objdump_sf.txt Disassembly of section .text: 0000000000272000 <.text>: 上面得到的偏移加上代码段起始地址后,得到在代码段中的位置分别为 0x1d6cb8 0x3dd054 0x3d64c8 0x3d20d8 0x3d52e8 root@localhost:/# llvm-addr2line-11 -f -e /system/bin/surfaceflinger 0x1d6cb8 ?? ??:0 root@localhost:/# llvm-addr2line-11 -f -e /system/bin/surfaceflinger 0x3dd054 ?? ??:0 root@localhost:/# llvm-addr2line-11 -f -e /system/bin/surfaceflinger 0x3d64c8 _ZNSt3__112__hash_tableINS_17__hash_value_typeINS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEEjEENS_22__unordered_map_hasherIS7_S8_NS_4hashIS7_EELb1EEENS_21__unordered_map_equalIS7_S8_N
S_8equal_toIS7_EELb1EEENS5_IS8_EEE8__rehashEm ??:0 root@localhost:/# root@localhost:/# llvm-addr2line-11 -f -e /system/bin/surfaceflinger 0x3d20d8 ?? ??:0 root@localhost:/# llvm-addr2line-11 -f -e /system/bin/surfaceflinger 0x3d52e8 ?? ??:0
只解析出来了一个,另外4个解析不出来!
三、补充
1. bpftrace 使用时elf后面可以跟函数地址,没必要跟函数名。比如上例直接使用 my_add_fun() 的地址 0x10f0。
root@localhost:/# bpftrace -e 'uprobe:/data/local/tmp/ebpf_test_add_on_inline:0x10f0 { printf("arg1=%d, arg2=%d\n", arg0, arg1); }' Attaching 1 probe... arg1=111, arg2=222 ^C
2. 还可以在函数后加偏移,此例中加4加8都是可以的,加12不行,因为加12后已经不在函数内了。
bpftrace -e 'uprobe:/data/local/tmp/ebpf_test_add_on_inline:my_add_fun+4 { printf("arg1=%d, arg2=%d\n", arg0, arg1); }'
3. 使用-lv加函数名称可以查看可探测函数的形参。但是只能使用函数名,使用 0x10f0 则不行。
root@localhost:/# bpftrace -lv 'u:/data/local/tmp/ebpf_test_add_on_inline:my_add_fun' uprobe:/data/local/tmp/ebpf_test_add_on_inline:my_add_fun int x int y root@localhost:/# bpftrace -lv 'u:/data/local/tmp/ebpf_test_add_on_inline:0x10f0' uprobe:/data/local/tmp/ebpf_test_add_on_inline: root@localhost:/#