zoukankan      html  css  js  c++  java
  • Linux内核编程、调试技巧小集【转】

    转自:https://www.cnblogs.com/arnoldlu/p/7152488.html

    1. 内核中通过lookup_symbol_name获取函数名称

    内核中很多结构体成员是函数,有时可能比较复杂不知道具体使用哪一个函数。这是可以通过lookup_symbol_name来获取符号表名称。

    复制代码
    int lookup_symbol_name(unsigned long addr, char *symname)
    {
        symname[0] = '';
        symname[KSYM_NAME_LEN - 1] = '';
    
        if (is_ksym_addr(addr)) {----------------------------------------地址有效性检查
            unsigned long pos;
    
            pos = get_symbol_pos(addr, NULL, NULL);
            /* Grab name */
            kallsyms_expand_symbol(get_symbol_offset(pos), symname);-----获取不好名称到symname
            return 0;
        }
        /* See if it's in a module. */
        return lookup_module_symbol_name(addr, symname);------------------从module符号表中查找
    }
    复制代码

    在timer_list.c和timer_stats.c中有使用,如下:

    复制代码
    static void print_name_offset(struct seq_file *m, unsigned long addr)
    {
        char symname[KSYM_NAME_LEN];
    
        if (lookup_symbol_name(addr, symname) < 0)
            seq_printf(m, "<%p>", (void *)addr);
        else
            seq_printf(m, "%s", symname);
    }
    复制代码

     2. 通过__builtin_return_address获取调用者函数地址

    2.1 背景介绍:__builtin_return_address是GCC提供的一个内置函数,用于判断给定函数的调用者。

    6.49 Getting the Return or Frame Address of a Function里面有更多获取函数调用者的介绍。

    void * __builtin_return_address (unsigned int level)

    level为参数,如果level为0,那么就是请求当前函数的返回地址;如果level为1,那么就是请求进行调用的函数的返回地址。

    2.2 使用实例

     内核中ftrace使用的较多:

    复制代码
    #define CALLER_ADDR0 ((unsigned long)__builtin_return_address(0))
    #define CALLER_ADDR1 ((unsigned long)return_address(1))
    #define CALLER_ADDR2 ((unsigned long)return_address(2))
    #define CALLER_ADDR3 ((unsigned long)return_address(3))
    #define CALLER_ADDR4 ((unsigned long)return_address(4))
    #define CALLER_ADDR5 ((unsigned long)return_address(5))
    #define CALLER_ADDR6 ((unsigned long)return_address(6))
    复制代码

     一个测试示例:

    复制代码
    #include <stdio.h>
    
    void func_e(void)
    {
        printf("func_e(0)=%p
    ", __builtin_return_address(0));-------------------------打印返回层级地址
        printf("func_e(1)=%p
    ", __builtin_return_address(1));
        printf("func_e(2)=%p
    ", __builtin_return_address(2));
        printf("func_e(3)=%p
    ", __builtin_return_address(3));
        printf("func_e(4)=%p
    ", __builtin_return_address(4));
        printf("func_e(5)=%p
    ", __builtin_return_address(5));
    }
    
    void func_d(void)
    {
        func_e();
    }
    
    void func_c(void)
    {
        func_d();
    }
    
    void func_b(void)
    {
        func_c();
    }
    
    void func_a(void)
    {
        func_b();
    }
    
    int main(int argc, char *agrv[])
    {
        func_a();
        printf("func_a=%p, func_b=%p, func_c=%p, func_d=%p, func_e=%p
    ", func_a, func_b, func_c, func_d, func_e);---------------------打印函数地址
    }
    复制代码

    执行结果如下:

    复制代码
    func_e(0)=0x4005f2
    func_e(1)=0x4005fd
    func_e(2)=0x400608
    func_e(3)=0x400613
    func_e(4)=0x400629
    func_e(5)=0x7fba4af1af45
    func_a=0x40060a, func_b=0x4005ff, func_c=0x4005f4, func_d=0x4005e9, func_e=0x40052d
    复制代码

    使用addr2line -e file -f addrs,可以看出编译是否-g的区别:

    gcc caller.c -o caller gcc caller.c -o caller -g

    addr2line -e caller -f 4005f2
    func_d
    ??:?

    addr2line -e caller -f 4005f2
    func_d
    /home/lubaoquan/temp/caller.c:16

    通过nm xxxx也可以找到地址对应的函数名:

    000000000040060a T func_a
    00000000004005ff T func_b
    00000000004005f4 T func_c
    00000000004005e9 T func_d
    000000000040052d T func_e

      

    参考文档:

    1.《Linux 内核中的 GCC 特性

    3. 基于HW Breakpoints的调试

    3.1 HW Breakpoints背景

    3.2

    参考文档:

    1. 《Hardware Breakpoint(or watchpoint) usage in Linux Kernel

    2. 《How Do Breakpoints Work

     4. likely和unlikely机制

    1.likely和unlikely背景

    likely和unlikely在include/linux/compiler.h中定义:

    复制代码
    #if defined(CONFIG_TRACE_BRANCH_PROFILING) ------------------------------------------------------------------带调试信息的likely和unlikelly
        && !defined(DISABLE_BRANCH_PROFILING) && !defined(__CHECKER__)
    ...
    # ifndef likely
    #  define likely(x)    (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 1))
    # endif
    # ifndef unlikely
    #  define unlikely(x)    (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 0))
    # endif
    ...
    #else
    # define likely(x)    __builtin_expect(!!(x), 1)--------------------------------------------------------------不带调试信息
    # define unlikely(x)    __builtin_expect(!!(x), 0)
    #endif
    复制代码

    __builtin_expect()是GCC从2.96开始支持的分支预测功能,降低因为指令跳转带来的分支下降,它的返回值就是它的第一个参数传递给它的值。

    2.机制详解

     __builtin_expect()通过改变汇编指令顺序,来充分利用处理器的流水线,直接执行最有可能的分支指令,而尽可能避免执行跳转指令(jmp)。因为jmp指令会刷新CPU流水线,而影响执行时间。

    复制代码
    #include <stdio.h>
    
    #define likely(x)    __builtin_expect(!!(x), 1)
    #define unlikely(x)  __builtin_expect(!!(x), 0)
    
    int main(char *argv[], int argc)
    {
       int a;
    
       /* Get the value from somewhere GCC can't optimize */
       a = atoi (argv[1]);
    
       if (unlikely (a == 2))--------------------------if (likely (a == 2))
          a++;
       else
          a--;
    
       printf ("%d
    ", a);
    
       return 0;
    }
    复制代码

    使用gcc xxx.c -o xxx -O2 -g编译。

    通过gdb xxxx -q,然后disassemble main可以看出两者区别,左边是unlikely,右边是likely。

    再来通过objdump -S xxx看一下结果。

    unlikely反汇编结果如下:

    复制代码
    Disassembly of section .text:
    
    00000000004004b0 <main>:
    
    #define likely(x)    __builtin_expect(!!(x), 1)
    #define unlikely(x)  __builtin_expect(!!(x), 0)
    
    int main(char *argv[], int argc)
    {
      4004b0:    48 83 ec 08              sub    $0x8,%rsp
       int a;
    
       /* Get the value from somewhere GCC can't optimize */
       a = atoi (argv[1]);
      4004b4:    48 8b 7f 08              mov    0x8(%rdi),%rdi
      4004b8:    31 c0                    xor    %eax,%eax
      4004ba:    e8 e1 ff ff ff           callq  4004a0 <atoi@plt>
    
       if (unlikely (a == 2))
      4004bf:    83 f8 02                 cmp    $0x2,%eax
      4004c2:    74 1b                    je     4004df <main+0x2f>--------------------如果cmp返回的结果是等于,就跳转到0x4004df地址,也即a++。
          a++;
       else
          a--;
      4004c4:    8d 50 ff                 lea    -0x1(%rax),%edx-----------------------不跳转的情况下,顺序执行a--这条指令。这种情况不需要跳转,一直到retq结束。
    }
    
    __fortify_function int
    printf (const char *__restrict __fmt, ...)
    {
      return __printf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
      4004c7:    be 54 06 40 00           mov    $0x400654,%esi-----------------------printf也是接着a--这条语句,也不需要跳转。
      4004cc:    bf 01 00 00 00           mov    $0x1,%edi
      4004d1:    31 c0                    xor    %eax,%eax
      4004d3:    e8 b8 ff ff ff           callq  400490 <__printf_chk@plt>
    
       printf ("%d
    ", a);
    
       return 0;
    }
      4004d8:    31 c0                    xor    %eax,%eax
      4004da:    48 83 c4 08              add    $0x8,%rsp
      4004de:    c3                       retq   
    
       /* Get the value from somewhere GCC can't optimize */
       a = atoi (argv[1]);
    
       if (unlikely (a == 2))
          a++;
      4004df:    ba 03 00 00 00           mov    $0x3,%edx----------------------------------对应a++这句指令。
      4004e4:    eb e1                    jmp    4004c7 <main+0x17>-------------------------跳转到printf这条指令,这种情况跳转了两次。
    复制代码

    likely反汇编结果如下:

    复制代码
    00000000004004b0 <main>:
    
    #define likely(x)    __builtin_expect(!!(x), 1)
    #define unlikely(x)  __builtin_expect(!!(x), 0)
    
    int main(char *argv[], int argc)
    {
      4004b0:    48 83 ec 08              sub    $0x8,%rsp
       int a;
    
       /* Get the value from somewhere GCC can't optimize */
       a = atoi (argv[1]);
      4004b4:    48 8b 7f 08              mov    0x8(%rdi),%rdi
      4004b8:    31 c0                    xor    %eax,%eax
      4004ba:    e8 e1 ff ff ff           callq  4004a0 <atoi@plt>
    
       if (likely (a == 2))
      4004bf:    83 f8 02                 cmp    $0x2,%eax
      4004c2:    75 1d                    jne    4004e1 <main+0x31>------------------不等于就跳转到a--,预测是等于2的情况。所以紧接的语句是a++。
          a++;
      4004c4:    ba 03 00 00 00           mov    $0x3,%edx---------------------------a++对应的指令。
    }
    
    __fortify_function int
    printf (const char *__restrict __fmt, ...)
    {
      return __printf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
      4004c9:    be 54 06 40 00           mov    $0x400654,%esi----------------------紧接着的是printf知道retq结束。
      4004ce:    bf 01 00 00 00           mov    $0x1,%edi
      4004d3:    31 c0                    xor    %eax,%eax
      4004d5:    e8 b6 ff ff ff           callq  400490 <__printf_chk@plt>
          a--;
    
       printf ("%d
    ", a);
    
       return 0;
    }
      4004da:    31 c0                    xor    %eax,%eax
      4004dc:    48 83 c4 08              add    $0x8,%rsp
      4004e0:    c3                       retq   
       a = atoi (argv[1]);
    
       if (likely (a == 2))
          a++;
       else
          a--;
      4004e1:    8d 50 ff                 lea    -0x1(%rax),%edx---------------------在cmp不等于情况下,跳转到此处。
      4004e4:    eb e3                    jmp    4004c9 <main+0x19>------------------a--之后再跳转回printf,两次跳转。
    复制代码

     3.总结

    如上汇编分析,__builtin_expect()的使用可以降低分置于句的跳转,按顺序执行,来减小对指令流水的刷新,从而加快程序的执行。

    当预测a最有可能是2时,a++的指令紧接着判断语句,顺序执行的可能性很大。

    当预测a最不可能是2是,a--的指令紧接着判断语句,a--被执行的可能性最大。

    参考文档:

    1. likely() and unlikely()

    2. linux kernel中likely和unlikely宏的机制分析

     5. 内存屏障barrier()和preempt_disable()

    联系方式:arnoldlu@qq.com
  • 相关阅读:
    小三角 + 右箭头【纯css】
    小程序自定义弹窗【解决点透问题】
    分页存储过程
    SQL如何用一条语句批量修改表中不同数据
    Dapper批量更新
    vue中 $event 的用法--获取当前父元素,子元素,兄弟元素
    T-sql语句查询执行顺序
    Lucene入门
    Spring Boot入门
    SSM-Spring一些知识点
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/11097153.html
Copyright © 2011-2022 走看看