LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

Linux内核编程、调试技巧小集

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

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

int lookup_symbol_name(unsigned long addr, char *symname)
{
    symname[0] = '\0';
    symname[KSYM_NAME_LEN - 1] = '\0';

    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\n", __builtin_return_address(0));-------------------------打印返回层级地址
    printf("func_e(1)=%p\n", __builtin_return_address(1));
    printf("func_e(2)=%p\n", __builtin_return_address(2));
    printf("func_e(3)=%p\n", __builtin_return_address(3));
    printf("func_e(4)=%p\n", __builtin_return_address(4));
    printf("func_e(5)=%p\n", __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\n", 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\n", 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\n", 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\n", 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()

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

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

posted on 2017-07-11 20:34  ArnoldLu  阅读(3407)  评论(0编辑  收藏  举报

导航