Linux内核编程、调试技巧小集
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 |
addr2line -e caller -f 4005f2 |
通过nm xxxx也可以找到地址对应的函数名:
000000000040060a T func_a
00000000004005ff T func_b
00000000004005f4 T func_c
00000000004005e9 T func_d
000000000040052d T func_e
参考文档:
3. 基于HW Breakpoints的调试
3.1 HW Breakpoints背景
3.2
参考文档:
1. 《Hardware Breakpoint(or watchpoint) usage in Linux Kernel》
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--被执行的可能性最大。
参考文档:
2. linux kernel中likely和unlikely宏的机制分析
5. 内存屏障barrier()和preempt_disable()
Linux内核编程、调试技巧小集的更多相关文章
- Linux内核编程规范与代码风格
source: https://www.kernel.org/doc/html/latest/process/coding-style.html translated by trav, travmym ...
- 初探linux内核编程,参数传递以及模块间函数调用
一.前言 我们一起从3个小例子来体验一下linux内核编程.如下: 1.内核编程之hello world 2.模块参数传递 3.模块间 ...
- Linux内核编程-0:来自内核的 HelloWorld
Linux内核编程一直是我很想掌握的一个技能.如果问我为什么,我也说不上来. 也许是希望有一天自己的ID也出现在内核开发组的邮件列表里?或是内核发行文件的CREDITS文件上? 也许是吧.其实更多的, ...
- Linux内核编程、调试技巧小集【转】
转自:https://www.cnblogs.com/arnoldlu/p/7152488.html 1. 内核中通过lookup_symbol_name获取函数名称 内核中很多结构体成员是函数,有时 ...
- linux内核编程笔记【原创】
以下为本人学习笔记,如有转载请注明出处,谢谢 DEFINE_MUTEX(buzzer_mutex); mutex_lock(&buzzer_mutex); mutex_unlock(& ...
- 宋宝华: Linux内核编程广泛使用的前向声明(Forward Declaration)
本文系转载,著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者:宋宝华 来源: 微信公众号linux阅码场(id: linuxdev) 前向声明 编程定律 先强调一点:在一切可 ...
- linux内核编程入门 hello world
注意: Makefile 文件的命名注意M需要大写,否则会报错. 在Makefile文件中make命令前应为tab制表符. 下文转载至:https://blog.csdn.net/bingqing07 ...
- linux内核编程入门--系统调用监控文件访问
参考的资料: hello world https://www.cnblogs.com/bitor/p/9608725.html linux内核监控模块--系统调用的截获 https://www. ...
- linux 内核态调试函数BUG_ON()[转]
一些内核调用可以用来方便标记bug,提供断言并输出信息.最常用的两个是BUG()和BUG_ON(). 当被调用的时候,它们会引发oops,导致栈的回溯和错误信息的打印.为什么这些声明会导致 oops跟 ...
随机推荐
- cocos2dv2项目转换为ARC支持的项目
原帖地址: http://www.cnblogs.com/liufan9/archive/2013/04/08/3006580.html
- linux下挂载U盘
转:http://www.cnblogs.com/yeahgis/archive/2012/04/05/2432779.html linux下挂载U盘 一.Linux挂载U盘: 1.插入u盘到计算机, ...
- 初涉IPC,了解AIDL的工作原理及使用方法
初涉IPC,了解AIDL的工作原理及使用方法 今天来讲讲AIDL,这个神秘的AIDL,也是最近在学习的,看了某课大神的讲解写下的blog,希望结合自己的看法给各位同价通俗易懂的讲解 官方文档:http ...
- 从Windows角度看Mac OS X上的软件开发
如果原来从事Windows软件开发,想跨足或转换至Mac OS X环境,需要知道那些东西?有什么知识技能可以快速运用在Mac OS X环境上的?这两个问题应该是Windows开发者进入Mac OS X ...
- java多继承
众所周知,java面向对象语言中只有单继承的编程语言,也许你会说,通过实现多个接口这种变通的方式达到多继承的目的.没错,你说的对,不过这并不是本片文章要说到的内容,本文要讲到的内容是java中实实在在 ...
- solr研磨之facet
作者:战斗民族就是干 转载请注明地址:http://www.cnblogs.com/prayers/p/8822417.html Facet 开门见山,facet解决的就是筛选,我是把它理解为一种聚合 ...
- spring mvc和spring的区别
springmvc只是spring其中的一部分. spring 可以 支持 hibernate ,ibatis ,JMS,JDBC 支持事务管理, 注解功能,表达式语言,测试 springmvc 就是 ...
- javaScript(6)---流程控制语句
学习要点: 1.语句的定义 2.if 语句 3.switch语句 4.do...while语句 5.while语句 6.for语句 7.for...in语句 8.break和continue语句 9. ...
- Java的运行原理
在Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器.这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口.编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由 ...
- 数据库scheme设计(9.4 小结)
通过这一章的内容,希望能够让大家明白一个道理,“数据库系统的性能不是优化出来的,更多的是设计出来的”.数据库Schema 的设计并不如很多人想象的那样只是一个简单的对象对应实现,而是一个系统工程.要想 ...