当ftrace用于用户空间

本文解析了如何在用户空间利用gcc的-mfentry选项实现ftracer,通过自定义__fentry__函数捕获函数调用参数和时间戳,展示了ftracer的实践应用。通过示例代码和测试,深入理解了追踪机制的工作原理。
摘要由CSDN通过智能技术生成

gcc4.6 添加了一个编译选项 -mfentry, 当程序编译之后,程序中的所有函数,除了notrace属性

#define notrace __attribute__((no_instrument_function))

的函数头上都会添加上call __fentry__,占用5个字节,__fentry__函数在程序中可以自定义, 比如在Linux kernel中被定义为 retq直接返回。

SYM_FUNC_START(__fentry__)
        retq
SYM_FUNC_END(__fentry__)

 定义成retq的意思是我不想直接使用__fentry__, 其实现也是在内核启动的时候把__fentry__换成了nopl, 然后在需要trace内核函数时,再替换成对应的trampoline(中文: 蹦床).

本篇讲解ftrace(function trace)在用户空间的应用。

以下代码来自此git工程:

https://github.com/x-lugoo/ftracer.git

ftracer.c中对__fentry__函数进行了自定义:

ftracer.c 

asm(
"       .globl __fentry__\n"
"__fentry__:\n"
/* save arguments */
"       push %rax\n"
"       push %rdi\n"
"       push %rsi\n"
"       push %rdx\n"
"       push %rcx\n"
"       push %r8\n"
"       push %r9\n"
"       movq %rsp,%rdi\n"
"       call ftracer\n"
"       pop %r9\n"
"       pop %r8\n"
"       pop %rcx\n"
"       pop %rdx\n"
"       pop %rsi\n"
"       pop %rdi\n"
"       pop %rax\n"
"       ret\n");

上面__fentry__函数的实现把所有传参寄存器(x86_64架构)全部压栈,然后把sp指针传给ftracer()的第一个参数.

__attribute__((used)) void ftracer(struct frame *fr)
{
        if (!tenabled)
                return;
        struct trace *t = &tbuf[tcur++];
        if (tcur >= TSIZE)
                tcur = 0;
        t->tstamp = __builtin_ia32_rdtsc();
        t->src = fr->caller;
        t->dst = fr->callee;
        t->arg1 = fr->rdi;
        t->arg2 = fr->rsi;
        t->arg3 = fr->rdx;
}struct frame {
        uint64_t r9; 
        uint64_t r8; 
        uint64_t rcx;
        uint64_t rdx;
        uint64_t rsi;
        uint64_t rdi;
        uint64_t rax;
        uint64_t callee;
        uint64_t caller;
};


其中callee是被调用函数地址,caller是调用函数地址 ,比如f1()调用f2(), f2函数头上调用了__fentry__,  那么__fentry__ 就可以从frame结构中的rax变量地址之后找到callee和caller

f1() {
  call f2

f2() {
  call __fentry__

ftracer()的实现把函数调用参数,被调用函数,调用函数,函数执行时间戳都存在tbuf

使用一个测试程序验证ftrace功能:

test.c 

#include "ftracer.h"

#define mb() asm volatile ("" ::: "memory")

void f3(int a, int b, int c)
{
        mb();
}
void f2(int a, int b, int c)
{
        f3(4, 5, 6); 
}
void f1(int a, int b, int c)
{
        f2(7, 8, 9); 
}
main()
{
        ftrace_dump_at_exit(0);
        ftrace_enable();
        f1(1, 2, 3); 
}

函数调用关系:main->f1->f2->f3

编译:

gcc -c ftracer.cgcc -pg -mfentry ftracer.o test.c -o test

执行./test的时候调用ftrace_dump(), 打印出tbuf中的数据,

void ftrace_dump(unsigned max)
                t = &tbuf[i];
                ...
                printf("%llx %llx->%llx %llx %llx %llx\n",
                                t->tstamp,
                                t->src, t->dst,
                                t->arg1, t->arg2, t->arg3);

tbuf中包含函数调用关系和函数执行时时间戳:

./test
2b4fcfe84137ab 4008d1->400893 4 5 6  (f2->f3)
2b4fcfe8413763 4008fe->4008ac 7 8 9  (f1->f2)
2b4fcfe84136ee 40092d->4008d9 1 2 3  (main->f1)

以上函数调用关系对应各个函数代码段:

   function f2:
   0x00000000004008a7 <+0>:    callq  0x400657 <__fentry__>
   0x00000000004008ac <+5>:    push   %rbp
   0x00000000004008ad <+6>:    mov    %rsp,%rbp
   ...
   0x00000000004008cc <+37>:    callq  0x40088e <f3>
   0x00000000004008d1 <+42>:    nop
   0x00000000004008d2 <+43>:    leaveq 
   0x00000000004008d3 <+44>:    retq   
   function f3:
   0x000000000040088e <+0>:    callq  0x400657 <__fentry__>
   0x0000000000400893 <+5>:    push   %rbp
  ...
   0x00000000004008a6 <+24>:    retq   
   function f1
   0x00000000004008d4 <+0>:    callq  0x400657 <__fentry__>
   0x00000000004008d9 <+5>:    push   %rbp
  ...
   0x00000000004008f4 <+32>:    mov    $0x7,%edi
   0x00000000004008f9 <+37>:    callq  0x4008a7 <f2>
   0x00000000004008fe <+42>:    nop
   0x00000000004008ff <+43>:    leaveq 
   0x0000000000400900 <+44>:    retq   
   function main
   0x0000000000400901 <+0>:    callq  0x400657 <__fentry__>
   ...
   0x0000000000400928 <+39>:    callq  0x4008d4 <f1>
   0x000000000040092d <+44>:    mov    $0x0,%eax
   0x0000000000400932 <+49>:    pop    %rbp
   0x0000000000400933 <+50>:    retq   

总结:以上分析了ftracer用于用户空间,可以跟踪函数调用参数和函数执行时间戳.

小编最新一直被催更微信公众号文章,我最近一直在设计优化tracer视频课程,内容已经迭代了四五次了,希望到时候能通俗易懂、图文并茂地讲解Linux内核中function tracer /function graph/ kprobe/kretprobe/trace event 的最底层原理和应用,预期三月下旬发布.

掌握之后将对Linux kernel的研究学习方式和debug方式带来很大的帮助,big picture 如下图所示:

本公众号持续分享实际工作和学习中关于linux内核的知识总结,偶尔也会出一些视频分享,前不久根据Linux实际工程中的底层需求,设计了一个视频《Linux常见锁和lockup检测机制》发布在了阅码场平台。点击左下角阅读原文可以一键报名和试看。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值