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

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

int lookup_symbol_name(unsigned long addr, char *symname)
{
symname[] = '\0';
symname[KSYM_NAME_LEN - ] = '\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 ;
}
/* 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) < )
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());-------------------------打印返回层级地址
printf("func_e(1)=%p\n", __builtin_return_address());
printf("func_e(2)=%p\n", __builtin_return_address());
printf("func_e(3)=%p\n", __builtin_return_address());
printf("func_e(4)=%p\n", __builtin_return_address());
printf("func_e(5)=%p\n", __builtin_return_address());
} 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()=0x4005f2
func_e()=0x4005fd
func_e()=0x400608
func_e()=0x400613
func_e()=0x400629
func_e()=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, ))
# endif
# ifndef unlikely
# define unlikely(x) (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, ))
# endif
...
#else
# define likely(x) __builtin_expect(!!(x), )--------------------------------------------------------------不带调试信息
# define unlikely(x) __builtin_expect(!!(x), )
#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[]); if (unlikely (a == ))--------------------------if (likely (a == 2))
a++;
else
a--; printf ("%d\n", a); return ;
}

使用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: ec sub $0x8,%rsp
int a; /* Get the value from somewhere GCC can't optimize */
a = atoi (argv[]);
4004b4: 8b 7f mov 0x8(%rdi),%rdi
4004b8: c0 xor %eax,%eax
4004ba: e8 e1 ff ff ff callq 4004a0 <atoi@plt> if (unlikely (a == ))
4004bf: f8 cmp $0x2,%eax
4004c2: 1b je 4004df <main+0x2f>--------------------如果cmp返回的结果是等于,就跳转到0x4004df地址,也即a++。
a++;
else
a--;
4004c4: 8d ff lea -0x1(%rax),%edx-----------------------不跳转的情况下,顺序执行a--这条指令。这种情况不需要跳转,一直到retq结束。
} __fortify_function int
printf (const char *__restrict __fmt, ...)
{
return __printf_chk (__USE_FORTIFY_LEVEL - , __fmt, __va_arg_pack ());
4004c7: be mov $0x400654,%esi-----------------------printf也是接着a--这条语句,也不需要跳转。
4004cc: bf mov $0x1,%edi
4004d1: c0 xor %eax,%eax
4004d3: e8 b8 ff ff ff callq <__printf_chk@plt> printf ("%d\n", a); return ;
}
4004d8: c0 xor %eax,%eax
4004da: c4 add $0x8,%rsp
4004de: c3 retq /* Get the value from somewhere GCC can't optimize */
a = atoi (argv[]); if (unlikely (a == ))
a++;
4004df: ba 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: ec sub $0x8,%rsp
int a; /* Get the value from somewhere GCC can't optimize */
a = atoi (argv[]);
4004b4: 8b 7f mov 0x8(%rdi),%rdi
4004b8: c0 xor %eax,%eax
4004ba: e8 e1 ff ff ff callq 4004a0 <atoi@plt> if (likely (a == ))
4004bf: f8 cmp $0x2,%eax
4004c2: 1d jne 4004e1 <main+0x31>------------------不等于就跳转到a--,预测是等于2的情况。所以紧接的语句是a++。
a++;
4004c4: ba mov $0x3,%edx---------------------------a++对应的指令。
} __fortify_function int
printf (const char *__restrict __fmt, ...)
{
return __printf_chk (__USE_FORTIFY_LEVEL - , __fmt, __va_arg_pack ());
4004c9: be mov $0x400654,%esi----------------------紧接着的是printf知道retq结束。
4004ce: bf mov $0x1,%edi
4004d3: c0 xor %eax,%eax
4004d5: e8 b6 ff ff ff callq <__printf_chk@plt>
a--; printf ("%d\n", a); return ;
}
4004da: c0 xor %eax,%eax
4004dc: c4 add $0x8,%rsp
4004e0: c3 retq
a = atoi (argv[]); if (likely (a == ))
a++;
else
a--;
4004e1: 8d 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()

Linux内核编程、调试技巧小集的更多相关文章

  1. Linux内核编程规范与代码风格

    source: https://www.kernel.org/doc/html/latest/process/coding-style.html translated by trav, travmym ...

  2. 初探linux内核编程,参数传递以及模块间函数调用

    一.前言                                  我们一起从3个小例子来体验一下linux内核编程.如下: 1.内核编程之hello world 2.模块参数传递 3.模块间 ...

  3. Linux内核编程-0:来自内核的 HelloWorld

    Linux内核编程一直是我很想掌握的一个技能.如果问我为什么,我也说不上来. 也许是希望有一天自己的ID也出现在内核开发组的邮件列表里?或是内核发行文件的CREDITS文件上? 也许是吧.其实更多的, ...

  4. Linux内核编程、调试技巧小集【转】

    转自:https://www.cnblogs.com/arnoldlu/p/7152488.html 1. 内核中通过lookup_symbol_name获取函数名称 内核中很多结构体成员是函数,有时 ...

  5. linux内核编程笔记【原创】

    以下为本人学习笔记,如有转载请注明出处,谢谢 DEFINE_MUTEX(buzzer_mutex); mutex_lock(&buzzer_mutex); mutex_unlock(& ...

  6. 宋宝华: Linux内核编程广泛使用的前向声明(Forward Declaration)

    本文系转载,著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者:宋宝华 来源: 微信公众号linux阅码场(id: linuxdev) 前向声明 编程定律 先强调一点:在一切可 ...

  7. linux内核编程入门 hello world

    注意: Makefile 文件的命名注意M需要大写,否则会报错. 在Makefile文件中make命令前应为tab制表符. 下文转载至:https://blog.csdn.net/bingqing07 ...

  8. linux内核编程入门--系统调用监控文件访问

    参考的资料: hello world   https://www.cnblogs.com/bitor/p/9608725.html linux内核监控模块--系统调用的截获  https://www. ...

  9. linux 内核态调试函数BUG_ON()[转]

    一些内核调用可以用来方便标记bug,提供断言并输出信息.最常用的两个是BUG()和BUG_ON(). 当被调用的时候,它们会引发oops,导致栈的回溯和错误信息的打印.为什么这些声明会导致 oops跟 ...

随机推荐

  1. anndroid 模糊引导界面

    先上两张图,后面补上代码 我们以前的写法是在需要显示模糊引导的地方,写一个布局,然后第一次使用的时候显示出来.但是这样做代码结构不清晰,所以我们有必要将这些View独立出来,写成一个自定义的View ...

  2. 测试access函数

    测试程序: 测试结果: chown root access.out 将用户ID改为root chmod u+s access.out 打开 set-user-ID位

  3. PS 滤镜——旋转模糊

    这里给出灰度图像的模糊算法,彩色图像只要分别对三个通道做模糊即可. %%  spin blur % 旋转模糊 clc; clear all; close all; I=imread('4.jpg'); ...

  4. 【Android 应用开发】Activity生命周期 与 Activity 之间的通信

    一. Activity生命周期 上图 1. Activity状态 激活状态 : Activity出于前台 , 栈顶位置; 暂停状态 : 失去了焦点 , 但是用户仍然可以看到 , 比如弹出一个对话框 , ...

  5. leetcode之旅(6)-Add Digits

    题目: Given a non-negative integer num, repeatedly add all its digits until the result has only one di ...

  6. Android开发 PopupWindow弹窗调用第三方地图(百度,高德)实现导航功能

    博客描述:后台返回地点的经纬度在地图上进行描点,点击导航弹出PopupWindow进行选择地图操作,如果手机中没有安装地图,提示没有,否则传值调起地图进行导航操作 看一下实现的效果,没图说再多都白搭 ...

  7. LambdaToSql 发布 兰姆达转换sql

    文档目录索引 查询.函数.分组.排序.分页 添加 Insert into 编辑 Update set 删除 Delete 生成实体 内置常用工具类库  文档完善中... 事务处理 Join 连接查询 ...

  8. RunTime运行时在iOS中的应用之UITextField占位符placeholder

    RunTime运行时机制 runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API. 在我们平时编写的Objective-C代码中, 程序运行过程时, 其实最终 ...

  9. VS2017安装包不占用C盘空间的方法,亲试

    问题:普通VS2017的安装方式,不论是在线安装还是下载的离线安装包,都会在安装过程中将安装包保存在C:\ProgramData\Microsoft\VisualStudio\Packages文件夹下 ...

  10. FFPLAY的原理(五)

    创建线程 Spawning Threads Overview Last time we added audio support by taking advantage of SDL's audio f ...