搜索

ebpf-3——调试Android Native代码栈回溯 - Hello-World3 -


发布时间: 2022-11-24 20:01:01    浏览次数:78 次

一、实验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:/#

 

免责声明 ebpf-3——调试Android Native代码栈回溯 - Hello-World3 - ,资源类别:文本, 浏览次数:78 次, 文件大小:-- , 由本站蜘蛛搜索收录2022-11-24 08:01:01。此页面由程序自动采集,只作交流和学习使用,本站不储存任何资源文件,如有侵权内容请联系我们举报删除, 感谢您对本站的支持。 原文链接:https://www.cnblogs.com/hellokitty2/p/16915885.html