首页
社区
课程
招聘
[原创]__builtin_return_address()引发的思考
2022-3-10 11:17 5947

[原创]__builtin_return_address()引发的思考

2022-3-10 11:17
5947

基本含义

__builtin_return_address(0)返回当前函数的返回地址

 

__builtin_return_address(1)返回当前函数的调用函数的返回地址

 

__builtin_return_address(2)返回当前函数的调用函数的调用函数的返回地址


源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void tt3(){
    LOGD("tt3 called  %p",__builtin_return_address(0));
    LOGD("tt3 called  %p",__builtin_return_address(1));
    LOGD("tt3 called  %p",__builtin_return_address(2));
}
 
void tt2(){
    LOGD("tt2 called  %p",__builtin_return_address(0));
    LOGD("tt2 called  %p",__builtin_return_address(1));
    tt3();
}
 
void tt1(){
    LOGD("tt1 called 0 %p",__builtin_return_address(0));
    tt2();
}

效果

void tt1()
tt1
void tt2()
tt2
void tt3()
tt3

 

由此可见就是使用 FP 串联了整个函数调用堆栈,上述的 函数(__builtin_return_address) 也就是通过串联起来的 [FP,#0x4] 操作完成了对上一级 LR 的查找


 

由此可以可以配合 dobby 对函数hook并打印出 lr 调用链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int level=4;
u_long lr0,lr1,lr2,lr3,lr4;
[&]()->void {
    // 调用这个 registerLR 本身也会触发一次函数调用 会有一个lr,故从1开始
    switch (level) {
        case 1:
            lr0 = reinterpret_cast<u_long>(__builtin_return_address(1));
            LOGD("LR -> %p",(void*)(lr0-soAddr));
            break;
        case 2:
            lr1 = reinterpret_cast<u_long>(__builtin_return_address(1));
            lr0 = reinterpret_cast<u_long>(__builtin_return_address(2));
            LOGD("LR -> %p %p",(void*)(lr0-soAddr),(void*)(lr1-soAddr));
            break;
        case 3:
            lr2 = reinterpret_cast<u_long>(__builtin_return_address(1));
            lr1 = reinterpret_cast<u_long>(__builtin_return_address(2));
            lr0 = reinterpret_cast<u_long>(__builtin_return_address(3));
            LOGD("LR -> %p %p %p",(void*)(lr0-soAddr),(void*)(lr1-soAddr),(void*)(lr2-soAddr));
            break;
        case 4:
            lr3 = reinterpret_cast<u_long>(__builtin_return_address(1));
            lr2 = reinterpret_cast<u_long>(__builtin_return_address(2));
            lr1 = reinterpret_cast<u_long>(__builtin_return_address(3));
            lr0 = reinterpret_cast<u_long>(__builtin_return_address(4));
            LOGD("LR -> %p %p %p %p",(void*)(lr0-soAddr),(void*)(lr1-soAddr),(void*)(lr2-soAddr),(void*)(lr3-soAddr));
            break;
        case 5:
            lr4 = reinterpret_cast<u_long>(__builtin_return_address(1));
            lr3 = reinterpret_cast<u_long>(__builtin_return_address(2));
            lr2 = reinterpret_cast<u_long>(__builtin_return_address(3));
            lr1 = reinterpret_cast<u_long>(__builtin_return_address(4));
            lr0 = reinterpret_cast<u_long>(__builtin_return_address(5));
            LOGD("LR -> %p %p %p %p %p",(void*)(lr0-soAddr),(void*)(lr1-soAddr),(void*)(lr2-soAddr),(void*)(lr3-soAddr),(void*)(lr4-soAddr));
            break;
    }
}();

这个写法比较简陋了,也算是偷懒借用内建函数(__builtin_return_address)完成了栈的回溯,当然也可以稍微麻烦一点,dobby 配合 asm内嵌汇编 完成每一个函数栈的拆解,本质都是用到了 FP 串联起了整个函数调用链这样一个大思路


补充

对于gcc 没法使用 大于0的参数 这里补充一个arm32版本的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#define xASM(x) __asm __volatile__ (x)
#define BRA(x) __builtin_return_address(x)
#define MAIN __attribute__((constructor))
#define NOINLINE __attribute__((__noinline__))
 
#if defined (__arm64__)||defined (__aarch64__)
 
    extern "C"
    void builtin_return_address(){
        // todo ...
    }
 
#elif defined(__arm__ )
 
    extern "C"
    void builtin_return_address(){
        void* FP = nullptr;
        void* lr0 = nullptr;
        void* lr1 = nullptr;
        void* lr2 = nullptr;
        void* lr3 = nullptr;
        xASM("MOV R4, LR":::"r4");
        xASM("MOV %0, r4":"=r"(lr0)::);
        xASM("MOV %0, FP":"=r"(FP)::);
 
        xASM("LDR R4, [R11]":::"r4");
        xASM("LDR %0, [R4,#4]":"=r"(lr1)::);
        xASM("CMP %0, #0"::"r"(lr1));
        xASM("BEQ END");
 
        xASM("LDR R4, [R11]":::"r4");
        xASM("LDR R4, [R4]":::"r4");
        xASM("LDR %0, [R4,#4]":"=r"(lr2)::);
        xASM("CMP %0, #0"::"r"(lr2));
        xASM("BEQ END");
 
        xASM("LDR R4, [R11]":::"r4");
        xASM("LDR R4, [R4]":::"r4");
        xASM("LDR R4, [R4]":::"r4");
        xASM("LDR %0, [R4,#4]":"=r"(lr3)::);
        xASM("CMP %0, #0"::"r"(lr3));
        xASM("BEQ END");
 
        xASM("END:MOV R0,R0");
        LOGE("LR BRA --->%p(FP) %p %p %p %p",FP,lr0,lr1,lr2,lr3);
    }
 
    NOINLINE
    void* test3(){
        LOGE("LR SRC ---> %p %p %p %p",BRA(3),BRA(2),BRA(1),BRA(0));
        builtin_return_address();
        return nullptr;
    }
 
    NOINLINE
    void* test2(){
        test3();
        return nullptr;
    }
 
    MAIN NOINLINE
    void test1(){
        test2();
    }
 
#else
 
#endif

简单看一下IDA帮我们反汇编的效果

 

实战效果,感觉还不错


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-3-11 12:49 被唱过阡陌编辑 ,原因: 评论区的问题完善
收藏
点赞4
打赏
分享
最新回复 (8)
雪    币: 6098
活跃值: (2277)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
breaklink 2022-3-10 16:43
2
0
arm64也通用吗?
雪    币: 1446
活跃值: (5154)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
唱过阡陌 1 2022-3-10 17:10
3
0
当然是适用的 ...
雪    币: 29
活跃值: (5110)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
不吃早饭 2022-3-10 19:00
4
0
然鹅这个函数在回溯等级大于0的情况下啥也拿不到,至少拿不到系统库的回溯
雪    币: 1446
活跃值: (5154)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
唱过阡陌 1 2022-3-10 22:16
5
0
不吃早饭 然鹅这个函数在回溯等级大于0的情况下啥也拿不到,至少拿不到系统库的回溯
gcc好像默认只能使用0 , 但是clang可以非0,用在ndk build安卓的so还是妥的 
实在不行你也可以考虑用dobby的 int DobbyInstrument(void *address, DBICallTy dbi_call);
然后不停的去读取r11加偏移来获取lr
雪    币: 29
活跃值: (5110)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
不吃早饭 2022-3-10 23:36
6
0
唱过阡陌 gcc好像默认只能使用0 , 但是clang可以非0,用在ndk build安卓的so还是妥的 实在不行你也可以考虑用dobby的 int DobbyInstrument(void *addres ...
其实可以直接用libart.so里的art::DumpNativeStack拿堆栈,很彻底
雪    币: 1446
活跃值: (5154)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
唱过阡陌 1 2022-3-11 10:00
7
0
不吃早饭 其实可以直接用libart.so里的art::DumpNativeStack拿堆栈,很彻底
不太懂具体该怎么操作:是直接改一下汇编乱跳一个值然后看logcat的崩溃日志?
但是感觉实践效果,看起来不太准确的样子
雪    币: 29
活跃值: (5110)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
不吃早饭 2022-3-11 10:42
8
1
唱过阡陌 不太懂具体该怎么操作:是直接改一下汇编乱跳一个值然后看logcat的崩溃日志? 但是感觉实践效果,看起来不太准确的样子

不是,直接从libart.so里解析这个导出函数,然后直接调用即可,这个函数要求传入一个std::ostream保存堆栈回溯结果,借此就能够获取任意线程的堆栈了,甚至允许获取其他线程的堆栈,不过那样需要对应线程的上下文(ucontext),因为这个函数实际就是异常信号里用来进行堆栈回溯的接口

雪    币: 29
活跃值: (5110)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
不吃早饭 2022-3-11 13:13
9
0
唱过阡陌 gcc好像默认只能使用0 , 但是clang可以非0,用在ndk build安卓的so还是妥的 实在不行你也可以考虑用dobby的 int DobbyInstrument(void *addres ...

clang一样,ndk默认使用的就是clang,获取不到结果是因为aosp对栈帧做了优化,clang那套东西对于系统库不适用,想要拿堆栈,还是直接用aosp内置接口比较好,ndk提供的Unwind系列接口同样存在这个问题

最后于 2022-3-11 13:14 被不吃早饭编辑 ,原因:
游客
登录 | 注册 方可回帖
返回